<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Hexo</title>
  
  <subtitle>Keep Learning</subtitle>
  <link href="http://example.com/atom.xml" rel="self"/>
  
  <link href="http://example.com/"/>
  <updated>2022-01-23T05:59:10.794Z</updated>
  <id>http://example.com/</id>
  
  <author>
    <name>Cleo</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>frp内网穿透</title>
    <link href="http://example.com/2022/01/23/frp/"/>
    <id>http://example.com/2022/01/23/frp/</id>
    <published>2022-01-23T05:13:10.000Z</published>
    <updated>2022-01-23T05:59:10.794Z</updated>
    
    <content type="html"><![CDATA[<p>通常，局域网如教育网内的主机无法通过教育网以外的机器进行连接，因此需要做内网穿透，即将内网的ip暴露在公网。frp 是一个专注于内网穿透的高性能的反向代理应用，支持 TCP、UDP、HTTP、HTTPS 等多种协议。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网，且frp是开源的，免费的，无需任何账户注册、登录，支持Linux、Mac、Windows等多个平台。本文介绍如何针对内网主机使用frp进行内网穿透。</p><h2 id="准备"><a href="#准备" class="headerlink" title="准备"></a>准备</h2><ul><li>在公网的主机A，可以是云服务器，也可以是自己的PC。</li><li>在内网的主机B，也就是需要访问的目的主机。</li></ul><h2 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h2><h3 id="主机A"><a href="#主机A" class="headerlink" title="主机A"></a>主机A</h3><p>这里以Ubuntu 18.04 LTS为例。首先从<a class="link"   href="https://github.com/fatedier/frp/releases" >frp官网<i class="fas fa-external-link-alt"></i></a>下载对应的包，注意一定要下载正确的架构。这里以v0.38.0为例:</p><figure class="highlight shell"><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">wget https://github.com/fatedier/frp/releases/download/v0.38.0/frp_0.38.0_linux_amd64.tar.gz</span><br><span class="line">tar -xzvf frp_0.38.0_linux_amd64.tar.gz</span><br><span class="line">cd frp_0.38.0_linux_amd64</span><br></pre></td></tr></table></figure><p>解压之后可以看frp目录下有这些文件:</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><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">frp_0.38.0_linux_amd64</span><br><span class="line">  |</span><br><span class="line">  |-- frpc              # 客户端程序</span><br><span class="line">  |-- frpc_full.ini     # 客户端完整配置文件</span><br><span class="line">  |-- frpc.ini          # 客户端配置文件</span><br><span class="line">  |-- frps              # 服务器程序</span><br><span class="line">  |-- frps_full.ini     # 服务器端完整配置文件</span><br><span class="line">  |-- frps.ini          # 服务器端配置文件</span><br><span class="line">  |-- LISENSE</span><br><span class="line">  |-- systemd</span><br><span class="line">        |</span><br><span class="line">        |-- xxx</span><br><span class="line">        |-- xxx</span><br></pre></td></tr></table></figure><p>主机A为公网能访问的服务器，我们将它作为代理，需要在主机A上运行frp的服务器端，也就是fprs。首先需要配置服务器端的配置文件，使用<code>vim frps.ini</code>进行编辑，内容如下:</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></pre></td><td class="code"><pre><span class="line"><span class="section">[common]</span></span><br><span class="line"><span class="attr">bind_port</span> = <span class="number">7000</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># dashboard</span></span><br><span class="line"><span class="attr">dashboard_port</span> = <span class="number">7500</span></span><br><span class="line"><span class="attr">dashboard_user</span> = admin</span><br><span class="line"><span class="attr">dashboard_pwd</span> = admin</span><br><span class="line"><span class="attr">enable_prometheus</span> = <span class="literal">true</span></span><br></pre></td></tr></table></figure><p><code>bind_port</code>为服务器程序运行时需要打开的端口，<code>dashboard_port</code>为后台管理程序的端口，用户名和密码可以自行设置。注意这两个端口都需要通过防火墙打开，Ubuntu 18.04 LTS打开命令为:</p><figure class="highlight shell"><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">sudo ufw allow 7000</span><br><span class="line">sudo ufw allow 7500</span><br><span class="line">sudo ufw reload</span><br></pre></td></tr></table></figure><p>保存配置后，就可以启动frps:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./frps -c frps.ini</span><br></pre></td></tr></table></figure><p>此时，可以访问<code>x.x.x.x:7500</code>来测试是否能进入dashboard，成功进入，说明启动成功。</p><h3 id="主机B"><a href="#主机B" class="headerlink" title="主机B"></a>主机B</h3><p>主机B为内网服务器，同样首先下载frp后解压，主机B需要运行frpc也就是frp客户端，因此先配置客户端配置文件，使用命令<code>vim frpc.ini</code>，内容如下:</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></pre></td><td class="code"><pre><span class="line"><span class="section">[common]</span></span><br><span class="line"><span class="attr">server_addr</span> = x.x.x.x  <span class="comment"># 这里填写你的主机A的公网IP地址</span></span><br><span class="line"><span class="attr">server_port</span> = <span class="number">7000</span></span><br><span class="line"></span><br><span class="line"><span class="section">[ssh]</span></span><br><span class="line"><span class="attr">type</span> = tcp</span><br><span class="line"><span class="attr">local_ip</span> = <span class="number">127.0</span>.<span class="number">0.1</span></span><br><span class="line"><span class="attr">local_port</span> = <span class="number">22</span></span><br><span class="line"><span class="attr">remote_port</span> = <span class="number">6000</span>     <span class="comment"># </span></span><br></pre></td></tr></table></figure><p>为主机B打开端口:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><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">sudo ufw allow 7000</span><br><span class="line">sudo ufw allow 22</span><br><span class="line">sudo ufw allow 6000</span><br><span class="line">sudo ufw reload</span><br></pre></td></tr></table></figure><p>为主机A打开额外的<code>6000</code>端口:</p><figure class="highlight shell"><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">sudo ufw allow 6000</span><br><span class="line">sudo ufw reload</span><br></pre></td></tr></table></figure><p>这里的<code>6000</code>端口配置文件中写的是<code>remote_port</code>，不太清楚指的是主机A还是主机B，我的理解应该是通过主机A的端口<code>6000</code>转发，这里就先都开放。</p><p>接着运行frp客户端程序:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./frpc -c frpc.ini</span><br></pre></td></tr></table></figure><h2 id="ssh连接测试"><a href="#ssh连接测试" class="headerlink" title="ssh连接测试"></a>ssh连接测试</h2><p>这里注意，端口号要使用6000，IP地址要用公网主机A的IP地址，用户名用内网主机B的用户名：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh -p 6000 username@x.x.x.x</span><br></pre></td></tr></table></figure><p>输入密码内网主机B的用户名对应的密码即可完成连接。</p><h2 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h2><h3 id="后台运行"><a href="#后台运行" class="headerlink" title="后台运行"></a>后台运行</h3><p>frpc和frps当终端退出时也就自动退出，为了能够一直在后台运行，可以使用nohup命令在后台挂起:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><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="meta">#</span><span class="bash"> 公网主机A</span></span><br><span class="line">nohup ./frps -c frps.ini &amp;</span><br><span class="line"><span class="meta">#</span><span class="bash"> 内网主机B</span></span><br><span class="line">nohup ./frpc -c frpc.ini &amp;</span><br></pre></td></tr></table></figure><h3 id="ssh免密登录"><a href="#ssh免密登录" class="headerlink" title="ssh免密登录"></a>ssh免密登录</h3><p>每次ssh登录都要输密码太麻烦，只要把本地主机的ssh公钥放在远程主机的<code>.ssh/authorized_keys</code>中即可，如果没有公钥，则生成:</p><figure class="highlight shell"><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">ssh-keygen -t rsa -C &quot;邮箱&quot;   # 或 ssh-keygen -o</span><br><span class="line">cat .ssh/id_rsa.pub</span><br></pre></td></tr></table></figure><p>将公钥复制后拷贝近远程主机的<code>.ssh/authorized_keys</code>中即可。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;通常，局域网如教育网内的主机无法通过教育网以外的机器进行连接，因此需要做内网穿透，即将内网的ip暴露在公网。frp 是一个专注于内网穿透的高性能的反向代理应用，支持 TCP、UDP、HTTP、HTTPS 等多种协议。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的</summary>
      
    
    
    
    <category term="环境搭建" scheme="http://example.com/categories/%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/"/>
    
    
    <category term="内网穿透" scheme="http://example.com/tags/%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F/"/>
    
  </entry>
  
  <entry>
    <title>Anaconda下PyTorch3D模块安装</title>
    <link href="http://example.com/2021/12/10/pytorch3d-install/"/>
    <id>http://example.com/2021/12/10/pytorch3d-install/</id>
    <published>2021-12-10T10:17:00.000Z</published>
    <updated>2021-12-12T13:49:04.283Z</updated>
    
    <content type="html"><![CDATA[<p>PyTorch3D是PyTorch对几何方向处理的封装库。笔者曾在安装时出错，这里记录一个比较容易成功的安装方法。</p><p>从Gitee上克隆仓库并安装:</p><figure class="highlight shell"><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="meta">$</span><span class="bash"> git <span class="built_in">clone</span> https://gitee.com/starstar365/pytorch3d.git</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> <span class="built_in">cd</span> pytorch3d &amp;&amp; pip install -e .</span></span><br></pre></td></tr></table></figure><p>这个版本并非最新的版本，经过实验，在1080Ti、Titan XP、3090和A4000上都能成功安装，不乏cuda版本为10.1和11.1。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;PyTorch3D是PyTorch对几何方向处理的封装库。笔者曾在安装时出错，这里记录一个比较容易成功的安装方法。&lt;/p&gt;
&lt;p&gt;从Gitee上克隆仓库并安装:&lt;/p&gt;
&lt;figure class=&quot;highlight shell&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class</summary>
      
    
    
    
    <category term="环境搭建" scheme="http://example.com/categories/%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/"/>
    
    
    <category term="PyTorch3D" scheme="http://example.com/tags/PyTorch3D/"/>
    
  </entry>
  
  <entry>
    <title>Ubuntu下PyTorch深度学习环境搭建</title>
    <link href="http://example.com/2021/12/10/pytorch-env/"/>
    <id>http://example.com/2021/12/10/pytorch-env/</id>
    <published>2021-12-10T09:55:45.000Z</published>
    <updated>2021-12-12T13:43:40.484Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Anaconda安装"><a href="#Anaconda安装" class="headerlink" title="Anaconda安装"></a>Anaconda安装</h2><p>进入<a class="link"   href="https://www.anaconda.com/products/individual" >Anaconda官网<i class="fas fa-external-link-alt"></i></a>下载Linux版本，通常选择<em>64-bit (x86) Installer</em>。下载后，赋予文件可执行权限后使用root权限安装:</p><figure class="highlight shell"><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="meta">$</span><span class="bash"> chmod +x Anaconda3-2021.11-Linux-x86_64.sh</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> sudo ./Anaconda3-2021.11-Linux-x86_64.sh</span></span><br></pre></td></tr></table></figure><h2 id="虚拟环境创建"><a href="#虚拟环境创建" class="headerlink" title="虚拟环境创建"></a>虚拟环境创建</h2><p>创建Python虚拟环境并激活:</p><figure class="highlight shell"><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="meta">$</span><span class="bash"> conda create -n pytorch python=3.7</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> conda activate pytorch</span></span><br></pre></td></tr></table></figure><h3 id="Anaconda常用命令"><a href="#Anaconda常用命令" class="headerlink" title="Anaconda常用命令"></a>Anaconda常用命令</h3><h4 id="查看版本号"><a href="#查看版本号" class="headerlink" title="查看版本号"></a>查看版本号</h4><figure class="highlight shell"><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="meta">$</span><span class="bash"> conda --version</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> 或</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> conda -V</span></span><br></pre></td></tr></table></figure><h4 id="环境管理"><a href="#环境管理" class="headerlink" title="环境管理"></a>环境管理</h4><ol><li><p>创建虚拟环境</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> conda create -n &lt;env_name&gt; python=&lt;python_version&gt; &lt;module1&gt; &lt;module2&gt; ...</span></span><br></pre></td></tr></table></figure></li><li><p>查看当前所有环境</p><figure class="highlight shell"><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="meta">$</span><span class="bash"> conda info -e</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> 或</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> conda env list</span></span><br></pre></td></tr></table></figure></li><li><p>激活某个环境</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> conda activate &lt;env_name&gt;</span></span><br></pre></td></tr></table></figure></li><li><p>退出当前环境</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> conda deactivate</span></span><br></pre></td></tr></table></figure></li><li><p>删除某个环境</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> conda remove --name &lt;env_name&gt; --all</span></span><br></pre></td></tr></table></figure></li><li><p>分享环境</p><p>将当前环境的所有配置导出到配置文件，通过配置文件创建一个与当前环境一模一样的环境。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><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="meta">#</span><span class="bash"> 将环境信息导出到.yml文件</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> conda env <span class="built_in">export</span> &gt; environment.yml</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> 从.yml文件创建环境</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> conda env create -f environment.yml</span></span><br></pre></td></tr></table></figure></li><li><p>清理（瘦身）</p><figure class="highlight shell"><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="meta">$</span><span class="bash"> conda clean -p        <span class="comment"># 删除没用的包，该命令会检查哪些包没有在包缓存中被硬依赖到其他地方并删除它们</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> conda clean -t        <span class="comment"># 删除conda保存下来的tar包</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> conda clean -y --all  <span class="comment"># 删除所有安装包及cache</span></span></span><br></pre></td></tr></table></figure></li><li><p>重命名</p><p>conda不提供重命名功能，只能通过clone-remove来实现。</p><figure class="highlight shell"><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="meta">$</span><span class="bash"> conda create -n newname --<span class="built_in">clone</span> oldname</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> conda remove --name oldname --all</span></span><br></pre></td></tr></table></figure><p>该操作只能在base环境下进行。</p></li></ol><h4 id="包管理"><a href="#包管理" class="headerlink" title="包管理"></a>包管理</h4><ol><li><p>查看当前环境下所有包</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> conda list</span></span><br></pre></td></tr></table></figure></li><li><p>查看非当前环境下的所有包</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> conda list -n &lt;env_name&gt;</span></span><br></pre></td></tr></table></figure></li><li><p>为当前环境安装某个包</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> conda install &lt;package_name&gt;</span></span><br></pre></td></tr></table></figure></li><li><p>安装本地包</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> conda install --use-local &lt;package_path&gt;</span></span><br></pre></td></tr></table></figure></li><li><p>更新包</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> conda update &lt;package_name&gt;</span></span><br></pre></td></tr></table></figure></li><li><p>卸载包</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> conda uninstall &lt;package_name&gt;</span></span><br></pre></td></tr></table></figure></li></ol><h2 id="PyTorch安装"><a href="#PyTorch安装" class="headerlink" title="PyTorch安装"></a>PyTorch安装</h2><p>版本需参考GPU支持和安装的cuda版本</p><p>PyTorch 1.8.0:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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="meta">#</span><span class="bash"> CUDA 10.2</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> conda install pytorch==1.8.0 torchvision==0.9.0 torchaudio==0.8.0 cudatoolkit=10.2 -c pytorch</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> CUDA 11.1</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> conda install pytorch==1.8.0 torchvision==0.9.0 torchaudio==0.8.0 cudatoolkit=11.1 -c pytorch -c conda-forge</span></span><br></pre></td></tr></table></figure><p>PyTorch 1.7.1:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">#</span><span class="bash"> CUDA 9.2</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> conda install pytorch==1.7.1 torchvision==0.8.2 torchaudio==0.7.2 cudatoolkit=9.2 -c pytorch</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> CUDA 10.1</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> conda install pytorch==1.7.1 torchvision==0.8.2 torchaudio==0.7.2 cudatoolkit=10.1 -c pytorch</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> CUDA 10.2</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> conda install pytorch==1.7.1 torchvision==0.8.2 torchaudio==0.7.2 cudatoolkit=10.2 -c pytorch</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> CUDA 11.0</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> conda install pytorch==1.7.1 torchvision==0.8.2 torchaudio==0.7.2 cudatoolkit=11.0 -c pytorch</span></span><br></pre></td></tr></table></figure><p>PyTorch 1.7.0:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">#</span><span class="bash"> CUDA 9.2</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> conda install pytorch==1.7.0 torchvision==0.8.0 torchaudio==0.7.0 cudatoolkit=9.2 -c pytorch</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> CUDA 10.1</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> conda install pytorch==1.7.0 torchvision==0.8.0 torchaudio==0.7.0 cudatoolkit=10.1 -c pytorch</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> CUDA 10.2</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> conda install pytorch==1.7.0 torchvision==0.8.0 torchaudio==0.7.0 cudatoolkit=10.2 -c pytorch</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> CUDA 11.0</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> conda install pytorch==1.7.0 torchvision==0.8.0 torchaudio==0.7.0 cudatoolkit=11.0 -c pytorch</span></span><br></pre></td></tr></table></figure><p>更多版本请参考<a class="link"   href="https://pytorch.org/get-started/previous-versions/" >PyTorch官网安装页面<i class="fas fa-external-link-alt"></i></a>.</p><h2 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h2><p>进入python交互式解释器:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> python</span></span><br></pre></td></tr></table></figure><p>执行GPU测试命令:</p><figure class="highlight python"><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> torch</span><br><span class="line">torch.cuda.is_available()</span><br></pre></td></tr></table></figure><p>返回True表示GPU可用，返回False表示GPU不可用。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;Anaconda安装&quot;&gt;&lt;a href=&quot;#Anaconda安装&quot; class=&quot;headerlink&quot; title=&quot;Anaconda安装&quot;&gt;&lt;/a&gt;Anaconda安装&lt;/h2&gt;&lt;p&gt;进入&lt;a class=&quot;link&quot;   href=&quot;https://www.</summary>
      
    
    
    
    <category term="环境搭建" scheme="http://example.com/categories/%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/"/>
    
    
    <category term="PyTorch" scheme="http://example.com/tags/PyTorch/"/>
    
  </entry>
  
  <entry>
    <title>Ubuntu安装oh-my-zsh</title>
    <link href="http://example.com/2021/12/10/oh-my-zsh/"/>
    <id>http://example.com/2021/12/10/oh-my-zsh/</id>
    <published>2021-12-10T09:32:08.000Z</published>
    <updated>2021-12-10T09:52:46.950Z</updated>
    
    <content type="html"><![CDATA[<h2 id="安装zsh"><a href="#安装zsh" class="headerlink" title="安装zsh"></a>安装zsh</h2><p>查看当前终端:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> <span class="built_in">echo</span> <span class="variable">$SHELL</span></span></span><br></pre></td></tr></table></figure><p>首先检查系统自带的终端:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> cat /etc/shells</span></span><br></pre></td></tr></table></figure><p>如果系统不自带zsh，则使用apt命令安装:</p><figure class="highlight shell"><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="meta">$</span><span class="bash"> apt update</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> apt install zsh</span></span><br></pre></td></tr></table></figure><p>切换默认终端:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> chsh -s /bin/zsh</span></span><br></pre></td></tr></table></figure><p>执行完上述步骤后退出登录或重启。</p><h2 id="安装oh-my-zsh"><a href="#安装oh-my-zsh" class="headerlink" title="安装oh-my-zsh"></a>安装oh-my-zsh</h2><p>安装wget:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> apt install wget</span></span><br></pre></td></tr></table></figure><p>从github安装oh-my-zsh:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> sh -c <span class="string">&quot;<span class="subst">$(wget -O- https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)</span>&quot;</span></span></span><br></pre></td></tr></table></figure><p>Github访问不了，可以使用Gitee:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> sh -c <span class="string">&quot;<span class="subst">$(wget -O- https://gitee.com/abeir/oh-my-zsh/raw/master/tools/install.sh)</span>&quot;</span></span></span><br></pre></td></tr></table></figure><p>安装完zsh后，环境变量保存在配置文件<code>~/.zshrc</code>中。</p><h2 id="安装插件"><a href="#安装插件" class="headerlink" title="安装插件"></a>安装插件</h2><h3 id="语法高亮洁zsh-syntax-highlighting"><a href="#语法高亮洁zsh-syntax-highlighting" class="headerlink" title="语法高亮洁zsh-syntax-highlighting"></a>语法高亮洁zsh-syntax-highlighting</h3><figure class="highlight shell"><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="meta">$</span><span class="bash"> git <span class="built_in">clone</span> https://github.com/zsh-users/zsh-syntax-highlighting.git</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> <span class="built_in">echo</span> <span class="string">&quot;source <span class="variable">$&#123;(q-)PWD&#125;</span>/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh&quot;</span> &gt;&gt; <span class="variable">$&#123;ZDOTDIR:-<span class="variable">$HOME</span>&#125;</span>/.zshrc</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> <span class="built_in">source</span> ~/.zshrc</span></span><br></pre></td></tr></table></figure><h2 id="结尾"><a href="#结尾" class="headerlink" title="结尾"></a>结尾</h2><p>如果碰到安装完后有些命令用不了，如conda等，需要查看原来的环境变量配置文件<code>~/.bashrc</code>，把里面相关的部分复制到<code>~/.zshrc</code>中即可。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;安装zsh&quot;&gt;&lt;a href=&quot;#安装zsh&quot; class=&quot;headerlink&quot; title=&quot;安装zsh&quot;&gt;&lt;/a&gt;安装zsh&lt;/h2&gt;&lt;p&gt;查看当前终端:&lt;/p&gt;
&lt;figure class=&quot;highlight shell&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td</summary>
      
    
    
    
    
    <category term="DevOps" scheme="http://example.com/tags/DevOps/"/>
    
  </entry>
  
  <entry>
    <title></title>
    <link href="http://example.com/2021/10/26/flow1/"/>
    <id>http://example.com/2021/10/26/flow1/</id>
    <published>2021-10-26T12:39:22.370Z</published>
    <updated>2021-10-29T15:44:17.037Z</updated>
    
    <content type="html"><![CDATA[<h1 id="流模型一-NICE"><a href="#流模型一-NICE" class="headerlink" title="流模型一: NICE"></a>流模型一: NICE</h1><p>NICE: Non-linear Independent Components Estimation是ICLR 2015 Workshop收录的论文，这篇文章是流模型的基石。流模型的本质是利用变量变换公式通过极大似然估计学习一个非线性可逆变换，从而将数据的分布变换到一个先验分布上，且该先验分布的随机变量的各维度相互独立，这个先验分布我们通常使用标准高斯分布。</p><p>假设数据样本$x \in X$，$x \in \mathbb{R}^D$，$x$的分布可以写成如下形式：</p><script type="math/tex; mode=display">p_X\left(x\right) = p_H\left(f\left(x\right)\right)|{\rm det} \frac{\part f\left(x\right)}{\part x}|</script><p>$h = f\left(x\right)$为一个可逆变换，即$x = f^{-1}\left(h\right)$。$p_X\left(x\right)$为数据样本集$X$的分布，$p_H\left(f\left(x\right)\right)$为先验分布，其各分量相互独立，$\frac{\part f\left(x\right)}{\part x}$为$f$对$x$的雅克比矩阵，$\rm det$为求矩阵的行列式。基于上述公式，我们采用极大似然估计求解$f\left(x\right)$，即</p><script type="math/tex; mode=display">\max \mathbb{E}_{x\in X}\left(\log p_X\left(x\right)\right)</script><p>根据优化目标，可以得到：</p><script type="math/tex; mode=display">\log p_X\left(x\right) = \log p_H\left(f\left(x\right)\right) + \log |{\rm det} \frac{\part f\left(x\right)}{\part x}|</script><p>NICE的目标就是使得雅克比行列式和$f^{-1}$好求。</p><h2 id="通用耦合层-General-Coupling-Layer"><a href="#通用耦合层-General-Coupling-Layer" class="headerlink" title="通用耦合层 General Coupling Layer"></a>通用耦合层 General Coupling Layer</h2><p>$x$有维度$D$，我们将$x$按维数分成两部分$x_1 \in \mathbb{R}^{d}$和$x_2 \in \mathbb{R}^{D-d}$，$m$为一个定义在$\mathbb{R}^d$上的函数，通用耦合层定义为：</p><script type="math/tex; mode=display">h_1 = x_1 \\h_2 = g\left(x_2; m\left(x_1\right)\right)</script><p>$g : \mathbb{R}^{D-d} \times m\left(\mathbb{R}^d\right)\to \mathbb{R}^{D - d} $。其逆运算为：</p><script type="math/tex; mode=display">x_1 = h_1 \\x_2 = g^{-1}\left(h_2; m\left(h_1\right)\right)</script><p>如此一来，雅克比矩阵变成了:</p><script type="math/tex; mode=display">\frac{\part y}{\part x} = \left[\begin{array}{l}I_d & 0 \\\frac{\part h_2}{\part x_1} & \frac{\part y_2}{\part x_2}\end{array}\right]</script><p>三角矩阵的行列式为对角线元素之和，因此${\rm det} \frac{\part f\left(x\right)}{\part x} = \frac{\part y_2}{\part x_2}$。</p><h3 id="加性耦合层-Adaptive-Coupling-Layer"><a href="#加性耦合层-Adaptive-Coupling-Layer" class="headerlink" title="加性耦合层 Adaptive Coupling Layer"></a>加性耦合层 Adaptive Coupling Layer</h3><p>加性耦合层如下：</p><script type="math/tex; mode=display">h_1 = x_1 \\h_2 = x_2 + m\left(x_1\right)</script><p>因此，雅克比行列式为1。且其逆运算为：</p><script type="math/tex; mode=display">x_1 = h_1 \\x_2 = h_2 - m(h_1)</script><p>只考虑上面单个耦合层变换，并不能获得很好的非线性，因此将多个简单变换进行组合，获得更强的非线性:</p><script type="math/tex; mode=display">x = h^{0} \leftrightarrow h^{1} \leftrightarrow h^{2} \leftrightarrow \cdots \leftrightarrow h^{n} = h</script><p>由链式法则可知，多个加性耦合层组合后$f$对$x$的雅克比行列式仍为1。</p><p>由于其中一部分为恒等变换，如果耦合的顺序不变，整个耦合的第一部分仍为恒等变换，信息没有充分混合，因此需要引入交叉耦合，充分混合信息，以达到更强的非线性。</p><h2 id="尺度变换层-Rescaling"><a href="#尺度变换层-Rescaling" class="headerlink" title="尺度变换层 Rescaling"></a>尺度变换层 Rescaling</h2><p>由于$f$为可逆变换，因此$x$与$h$的维度一致，$h$是一个先验分布，可以认为它遍布整个$D$维空间，虽然$x$也同样具有$D$维，但它未必就真能遍布整个$D$维空间。流这种基于可逆变换的模型，天生就存在比较严重的维度浪费问题：输入数据明明都不是D维流形，但却要编码为一个D维流形。</p><p>为了解决这个问题，NICE引入尺度变换层，对编码出来的特征的每个维度都做尺度变换，即</p><script type="math/tex; mode=display">h = s \odot h^{n}</script><p>$s = {s_1, s_2, \dots, s_D}$。$s$也是一个要优化的参数向量，且其各分量非负，因此，雅克比行列式等于$\sum_{i=1}^D s_i$，优化目标为最大化：</p><script type="math/tex; mode=display">\log p_X\left(x\right) = \log\left(p_H\left(h\right)\right) + \sum_{i = 1}^{D}s_i</script><p>假设$p_H\left(h\right)$为正态分布，引入$s$相当于引入了方差作为参数，$s_i = \frac{1}{\sigma_i}$，$s_i$越大，方差越小，该维度的变化越小，如果方差为0，那么该特征就恒为均值0，该维度的分布坍缩为一个点，于是这意味着流形减少了一维。</p><h2 id="特征解耦"><a href="#特征解耦" class="headerlink" title="特征解耦"></a>特征解耦</h2><p>设置先验分布为各分量独立：</p><script type="math/tex; mode=display">\log p_X\left(x\right) = \sum_{d = 1}^{D} \log p_{H_d}\left(f_d\left(x\right)\right) + \log |{\rm det}\frac{\part f\left(x\right)}{\part x}| \\f\left(x\right) = \left(f_d\left(x\right)\right)_{d \le D}</script><p>实际上实现学习到一个好的特征，理想情况下各个维度之间应该是相互独立的，这样实现了特征的解耦，使得每个维度都有自己独立的含义。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;流模型一-NICE&quot;&gt;&lt;a href=&quot;#流模型一-NICE&quot; class=&quot;headerlink&quot; title=&quot;流模型一: NICE&quot;&gt;&lt;/a&gt;流模型一: NICE&lt;/h1&gt;&lt;p&gt;NICE: Non-linear Independent Components </summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>UNIX操作系统高级编程</title>
    <link href="http://example.com/2021/05/16/unix/"/>
    <id>http://example.com/2021/05/16/unix/</id>
    <published>2021-05-16T15:53:53.000Z</published>
    <updated>2021-05-26T15:12:08.037Z</updated>
    
    <content type="html"><![CDATA[<h1 id="UNIX环境高级编程"><a href="#UNIX环境高级编程" class="headerlink" title="UNIX环境高级编程"></a>UNIX环境高级编程</h1><h2 id="第1章-UNIX基础知识"><a href="#第1章-UNIX基础知识" class="headerlink" title="第1章 UNIX基础知识"></a>第1章 UNIX基础知识</h2><h3 id="1-1-Unix体系结构"><a href="#1-1-Unix体系结构" class="headerlink" title="1.1 Unix体系结构"></a>1.1 Unix体系结构</h3><ul><li>内核：一种软件，控制计算机硬件资源，提供程序运行环境</li><li>系统调用：内核的接口</li><li>shell，公用函数库</li><li>应用程序</li></ul><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210510145922442.png" alt="image-20210510145922442"></p><h3 id="1-2-不带缓冲的I-O"><a href="#1-2-不带缓冲的I-O" class="headerlink" title="1.2 不带缓冲的I/O"></a>1.2 不带缓冲的I/O</h3><p>函数open、read、write、lseek以及close提供了不带缓冲的I/O。这些函数都使用<strong>文件描述符</strong>。</p><h4 id="1-2-1-文件描述符"><a href="#1-2-1-文件描述符" class="headerlink" title="1.2.1 文件描述符"></a>1.2.1 文件描述符</h4><p><code>STDIN_FILENO</code>、<code>STDOUT_FILENO</code>为两个宏，值分别为0、1，表示标准输入和标准输出的文件描述符。</p><h4 id="1-2-1-read"><a href="#1-2-1-read" class="headerlink" title="1.2.1 read"></a>1.2.1 read</h4><p>read函数返回读取的字节数，当到达文件的尾端时，read返回0；如果发生了一个读错误，read返回-1。</p><h3 id="1-3-标准I-O"><a href="#1-3-标准I-O" class="headerlink" title="1.3 标准I/O"></a>1.3 标准I/O</h3><p>标准I/O函数为那些不带缓冲的I/O函数提供了一个带缓冲的接口。使用标准I/O函数无需担心如何选取最佳的缓冲区大小。使用标准I/O还简化了对输入行的处理，如fgets读取一个完整的行，而read函数读取指定的字节数。</p><p><code>stdin</code>、<code>stdout</code>、getc、putc等均在<code>stdio.h</code>中定义。</p><h3 id="1-4-进程控制"><a href="#1-4-进程控制" class="headerlink" title="1.4 进程控制"></a>1.4 进程控制</h3><p>程序（program）是一个存储在磁盘上的某个目录中的可执行文件。程序的执行实例被称为进程（process）。每个进程都有一个唯一的数字标识符，称为进程ID，进程ID总是一个非负整数。</p><ul><li>getpid()</li><li>getgid()</li><li>fork()</li><li>waitpid()</li><li>exec()</li></ul><p>一个进程内的所有线程共享同一地址空间、文件描述符、栈以及与进程相关的属性，它们能访问同一存储区，因此各个线程在访问共享数据时需要采取同步措施以避免不一致性。</p><h3 id="1-5-出错处理"><a href="#1-5-出错处理" class="headerlink" title="1.5 出错处理"></a>1.5 出错处理</h3><p>头文件errno.h中定义了<code>errno</code>以及可以赋与它的各种常量，这些常量都以字符E开头。当UNIX系统函数出错时，通常会返回一个负值，而且整形变量<code>errno</code>通常被设置为具有特定信息的值。</p><p>对于<code>errno</code>应当注意两条规则：</p><ul><li>如果没有出错，其值不会被例程清除。因此，仅当函数的返回值指明出错时，才检验其值。</li><li>任何函数都不会将<code>errno</code>值设置为0，而且errno.h中定义的所有常量都不为0。</li></ul><p>C标准定义了2个函数，它们用于打印出错信息：</p><p>1 - strerror</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;string.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">char</span> *<span class="title">strerror</span><span class="params">(<span class="keyword">int</span> errnum)</span></span>;</span><br></pre></td></tr></table></figure><p>strerror函数将errnum（通常就是errno值）映射为一个出错消息字符串，并且返回此字符串的指针。</p><p>2 - perror</p><p>perror函数基于errno的当前值，在标准错误上产生一条出错消息，然后返回。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">perror</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *msg)</span></span>;</span><br></pre></td></tr></table></figure><h3 id="1-6-时间值"><a href="#1-6-时间值" class="headerlink" title="1.6 时间值"></a>1.6 时间值</h3><p>Unix系统使用过两种不同的时间值：</p><ol><li><p>日历时间，即UTC，格林尼治标准时间，以秒为单位，可以用于记录文件的最近修改时间，对应类型为<code>time_t</code>。</p></li><li><p>进程时间，也被称为CPU时间，用以度量进程使用的<strong>中央处理器资源</strong>。进程时间以时钟滴答计算。对应数据类型为<code>clock_t</code>。</p></li></ol><p>当度量一个进程的执行时间时，UNIX系统为一个进程维护了3个进程时间值：</p><ol><li><p>时钟时间，是进程运行的时间总量，其值与系统中同时运行的进程数有关。</p></li><li><p>用户CPU时间，是执行用户指令所用的时间量。</p></li><li><p>系统CPU时间，是该进程执行内核程序所经历的时间。</p></li></ol><p>用户CPU时间和系统CPU时间之和常被称为<strong>CPU时间</strong>。</p><p>time命令用于获取任一进程的时钟时间、用户时间和系统时间。</p><h3 id="1-7-信号"><a href="#1-7-信号" class="headerlink" title="1.7 信号"></a>1.7 信号</h3><p>可以使用kill函数产生信号，在一个进程中调用此函数就可以向另一个进程发送一个信号。当然，这样做也有些限制：当向一个进程发送信号时，我们必须是那个进程的所有者或者是超级用户。</p><h2 id="第3章-文件I-O"><a href="#第3章-文件I-O" class="headerlink" title="第3章 文件I/O"></a>第3章 文件I/O</h2><h3 id="3-1-引言"><a href="#3-1-引言" class="headerlink" title="3.1 引言"></a>3.1 引言</h3><p>本章内容：</p><ul><li><p>文件I/O。本章介绍的I/O函数通常被称为不带缓冲的I/O（unbuffered I/O），所谓不带缓冲指的是每个read和write都调用内核中的一个系统调用。</p><ul><li>open</li><li>read</li><li>write</li><li>lseek</li><li>close</li></ul></li><li><p>多个进程间共享资源。</p><ul><li>dup</li><li>fcntl</li><li>sync</li><li>fsync</li><li>ioctl</li></ul></li></ul><h3 id="3-2-文件描述符"><a href="#3-2-文件描述符" class="headerlink" title="3.2 文件描述符"></a>3.2 文件描述符</h3><p>对内核而言，所有打开的文件都通过文件描述符引用。文件描述符是一个非负整数。当打开一个现有文件或创建一个新文件时，内核向进程返回一个文件描述符。</p><ul><li>文件描述符0——标准输入， <code>STDIN_FILENO</code></li><li>文件描述符1——标准输出， <code>STDOUT_FILENO</code></li><li>文件描述符2——错误输出， <code>STDERR_FILENO</code></li></ul><p>上述三个符号常量存储在头文件<code>unistd.h</code>中。</p><h3 id="3-3-函数open和openat"><a href="#3-3-函数open和openat" class="headerlink" title="3.3 函数open和openat"></a>3.3 函数open和openat</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;fcntl.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">open</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *path, <span class="keyword">int</span> oflag, ... <span class="comment">/* mode_t mode */</span>)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">openat</span><span class="params">(<span class="keyword">int</span> fd, <span class="keyword">const</span> <span class="keyword">char</span> *path, <span class="keyword">int</span> oflag, ... <span class="comment">/*mode_t mode */</span>)</span></span>;</span><br></pre></td></tr></table></figure><p>若成功，返回文件描述符；若出错，返回-1。</p><p><code>...</code>表示该函数的余下参数的数量及类型是可变的。对于open函数而言，仅当创建新文件时才使用最后这个参数。</p><p>oflag:</p><ul><li>O_RDONLY：只读</li><li>O_WRONLY：只写</li><li>O_RDWR：可读可写</li><li>O_EXEC：只执行打开</li><li>O_SEARCH：只搜索打开（应用于目录）</li></ul><p>上述五个常量中必须指定一个且只能指定一个，下列常量则是可选的：</p><ul><li>O_APPEND：追加模式</li><li>O_CREATE：若文件不存在则创建它，此时需要指定mode</li><li><p>O_TRUNC：文件存在，且为只写或读-写成功打开，则将其长度截断为0，于O_APPEND对应。</p></li><li><p>O_DIRECTORY：如果path引用的不是目录，则出错</p></li><li>O_EXCL：如果同时指定了O_CRAET，而文件已经存在，则出错。可以用于测试一个文件是否存在，如果不存在则创建，这使测试和创建两者成为一个原子操作。</li></ul><ul><li>O_SYNC：使每次write等待物理I/O操作完成，包括由该write操作引起的文件属性更新所需的I/O。</li><li>O_DSYNC：使每次write要等待物理I/O操作完成，但是如果该写操作并不影响读取刚写入的数据，则不需等待文件属性被更新。</li><li>O_RSYNC：使每一个以文件描述符作为参数进行的read操作等待，直至所有对文件同一部分挂起的写操作都完成。</li></ul><ul><li>O_CLOEXEC：把FD_CLOEXEC常量设置为文件描述符标志</li><li>O_NOCTTY：如果path引用的是终端设备，则不将该设备分配作为此进程的控制终端。</li><li>O_NOFOLLOW：如果path是一个链接符号，则出错。</li><li>O_NONBLOCK：如果path是一个FIFO、一个快特殊文件或者一个字符特殊文件，则此选项为文件的本次打开操作和后续的I/O操作设置非阻塞模式。</li><li>O_TTY_INIT：如果打开一个还未打开的终端设备，设置非标准termios参数值，使其符合Single UNIX Specification。</li></ul><p>关于openat的三种情况：</p><ol><li>如果path为绝对路径，则fd没有任何作用，传任何值都可以，此时与open函数无异。</li><li>如果path为相对路径，而fd为<code>AT_FDCWD</code>，则指定目录为当前目录。</li><li>如果path为相对路径，fd不为<code>AT_FDCWD</code>，则需给fd传入指定的目录的文件描述符。</li></ol><p>TOCTTOU（time-of-check-to-time-of-use），如果有两个基于文件的函数调用，其中第二个调用依赖于第一个调用的结果，那么程序是脆弱的，因为两个基于文件的函数调用，在两个函数调用之间文件可能改变了。</p><h3 id="3-4-函数create"><a href="#3-4-函数create" class="headerlink" title="3.4 函数create"></a>3.4 函数create</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;fcntl.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">creat</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> * path, <span class="keyword">mode_t</span> mode)</span></span>;</span><br></pre></td></tr></table></figure><p>若成功，返回一个<strong>只写打开</strong>的文件描述符；若出错，返回-1。</p><p>此函数等效于：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">open(path, O_WRONLY | O_CREAT | O_TRUNC, mode);</span><br></pre></td></tr></table></figure><p>create只能以只写方式打开所创建的文件，要想是写可读可写必须用open函数：</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">open(path, O_RDWR | O_CREAT | O_TRUNC, mode);</span><br></pre></td></tr></table></figure><h3 id="3-5-函数close"><a href="#3-5-函数close" class="headerlink" title="3.5 函数close"></a>3.5 函数close</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">close</span><span class="params">(<span class="keyword">int</span> fp)</span></span>;</span><br></pre></td></tr></table></figure><p>若成功，返回0；若出错，则返回-1。</p><h3 id="3-6-函数lseek"><a href="#3-6-函数lseek" class="headerlink" title="3.6 函数lseek"></a>3.6 函数lseek</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">off_t</span> <span class="title">lseek</span><span class="params">(<span class="keyword">int</span> fd, <span class="keyword">off_t</span> offset, <span class="keyword">int</span> whence)</span></span>;</span><br></pre></td></tr></table></figure><p>若成功，返回新的文件偏移量；若出错，返回-1。</p><p>按系统默认情况，当打开一个文件时，除非指定O_APPEND选项，否则该偏移量被设置为0。</p><p>whence的三种情况：</p><ul><li>whence 为 SEEK_SET，则将文件偏移量设置为距文件开始处offset字节。</li><li>whence 为 SEEK_CUR，则将文件偏移量设置为当前值加offset，offset可为正或负。</li><li>whence 为 SEEN_END，则将文件偏移量设置为文件长度加offset，offset可正可负。</li></ul><p>确定打开文件的当前偏移量：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">off_t</span> currpos;</span><br><span class="line">currpos = lseek(fd, <span class="number">0</span>, SEEK_CUR);</span><br></pre></td></tr></table></figure><p>这种方法也可以用来测试所涉及的文件是否可以设置偏移量，如果文件描述符指向的是一个管道、FIFO或网络套接字，则lseek返回-1，并将errno设置为ESPIPE。</p><p>在比较lseek的返回值时应当谨慎，不要测试它是否小于0，而要测试它是否等于<code>-1</code>。</p><p>lseek仅将当前的文件描述符记录在内核中，它并<strong>不引起任何I/O操作</strong>。然后，该偏移量用于下一个读或写操作。</p><p>文件偏移量可以大于文件的当前长度，在这种情况下，对该文件的下一次写将加长该文件，并在文件中构成一个空洞，这一点是允许的。位于文件中但没有写过的字节都被读为0。文件中的空洞并不要求在磁盘上占用存储区。具体处理方式与文件系统的实现有关，当定位超出文件尾端后写时，对于新写的数据需要分配磁盘快，但对于原文件尾和新开始写位置之间的部分则不需要分配磁盘块。</p><p>命令<code>od -c</code>以字符方式查看文件内容。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210511090536433.png" alt="image-20210511090536433"></p><h3 id="3-7-函数read"><a href="#3-7-函数read" class="headerlink" title="3.7 函数read"></a>3.7 函数read</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">ssize_t</span> <span class="title">read</span><span class="params">(<span class="keyword">int</span> fd, <span class="keyword">void</span> *buf, <span class="keyword">size_t</span> nbytes)</span></span>;</span><br></pre></td></tr></table></figure><p>fd为文件描述符，buf为缓冲区，nbytes为要求读的字节数。返回读到的字节数，若到达文件尾，返回0；若出错，返回-1。</p><h3 id="3-8-函数write"><a href="#3-8-函数write" class="headerlink" title="3.8 函数write"></a>3.8 函数write</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">ssize_t</span> <span class="title">write</span><span class="params">(<span class="keyword">int</span> fd, <span class="keyword">const</span> <span class="keyword">void</span> *buf, <span class="keyword">size_t</span> nbyts)</span></span>;</span><br></pre></td></tr></table></figure><p>fd为文件描述符，buf为包含数据的缓冲区，nbytes为要写入的字节数。若成功，返回已经写的字节数；若出错，返回-1。</p><p>返回值通常与nbytes一致，否则表示出错。write出错的一个常见原因时磁盘已写满，或者超过了一个给定进程的文件长度限制。</p><h3 id="3-9-I-O的效率"><a href="#3-9-I-O的效率" class="headerlink" title="3.9 I/O的效率"></a>3.9 I/O的效率</h3><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210511091215956.png" alt="image-20210511091215956"></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;apue.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;string.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">str2int</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">char</span> *argv[])</span></span></span><br><span class="line"><span class="function"></span>&#123;   </span><br><span class="line">    <span class="keyword">int</span> n, buff_size;</span><br><span class="line">    buff_size = str2int(argv[<span class="number">1</span>]);</span><br><span class="line">    <span class="keyword">char</span> *buf = (<span class="keyword">char</span> *)<span class="built_in">malloc</span>(buff_size * <span class="keyword">sizeof</span>(<span class="keyword">char</span>));</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;%d\n&quot;</span>, buff_size);</span><br><span class="line"></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> ((n = read(STDIN_FILENO, buf, buff_size)) &gt; <span class="number">0</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (write(STDOUT_FILENO, buf, n) != n)</span><br><span class="line">            err_sys(<span class="string">&quot;write error&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (n &lt; <span class="number">0</span>)</span><br><span class="line">        err_sys(<span class="string">&quot;read error&quot;</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (but != <span class="literal">NULL</span>)</span><br><span class="line">    <span class="built_in">free</span>(buf);</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><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">str2int</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *str)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> length = <span class="built_in">strlen</span>(str), n = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; length; i++)</span><br><span class="line">    &#123;</span><br><span class="line">        n = n * <span class="number">10</span> + (str[i] - <span class="string">&#x27;0&#x27;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> n;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>测试结果：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210511092423651.png" alt="image-20210511092423651"></p><h3 id="3-10-文件共享"><a href="#3-10-文件共享" class="headerlink" title="3.10 文件共享"></a>3.10 文件共享</h3><p>内核使用3种数据结构表示打开文件：</p><ol><li>每个进程在<strong>进程表</strong>中都有一个记录项（<strong>进程表项</strong>），记录项中包含一张<strong>打开文件描述符表</strong>，可将其视为一个矢量，每个描述符占用一项。每一项包含：<ol><li>文件描述符标志</li><li>指向一个<strong>文件表项</strong>的指针</li></ol></li><li>内核为所有打开文件维持一张<strong>文件表</strong>，每个文件表项包含：<ol><li>文件状态标志</li><li>当前文件偏移量</li><li>指向该文件<strong>v节点表项</strong>的指针</li></ol></li><li>每个打开文件都有一个v节点（v-node）结构。v节点包含了文件类型和对此文件进行各种操作函数的指针。对于大多数文件，v节点还包含了该文件的<strong>i节点</strong>（i-node，索引节点）。i-node包含了文件的所有者、文件长度、指向文件实际数据块在磁盘上所在位置的指针等。</li></ol><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210511094237554.png" alt="image-20210511094237554"></p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210511094302763.png" alt="image-20210511094302763"></p><p>我们假定第一个进程在文件描述符3上打开该文件，而另一个进程在文件描述符4上打开该文件。打开该文件的每个进程都获得各自的一个文件表项，但对一个给定的文件只有一个v节点表项。之所以每个进程都获得自己的文件表项，是因为这可以使每个进程都有它自己的对该文件的当前偏移量。</p><ul><li>在完成每个write后，在文件表项中的当前偏移量即增加所写入的字节数。如果这导致当前文件偏移量超过了当前文件长度，则将i节点表项中的<strong>当前文件长度</strong>设置为当前文件偏移量（也就是该文件加长了）。</li><li>如果使用O_APPEND标志打开一个文件，则相应标志也被设置到文件表项的文件标志状态中。每次对这种具有追加写标志的文件执行写操作时，文件表项中的当前文件偏移量首先会被设置为i节点表项中的文件长度。这就使得每次写入的数据都追加到文件的当前尾端处。</li><li>若一个文件用lseek定位到文件当前的尾端，则文件表项中的当前文件偏移量被设置成i节点表项中的当前文件长度。（注意，这与用O_APPEND标志打开文件是不同的。）</li><li>lseek函数只修改文件表项中的当前文件偏移量，不进行任何I/O操作。</li></ul><p>可能有多个文件描述符项指向同一个文件表项，如fork后父子进程各自的每一个打开文件描述符共享同一个文件表项。</p><h3 id="3-11-原子操作"><a href="#3-11-原子操作" class="headerlink" title="3.11 原子操作"></a>3.11 原子操作</h3><p>一个原子操作的例子：两个进程打开同一个文件，都先调用lseek，再调用write写文件，可能会导致覆盖。</p><p>函数pread和pwrite：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 返回值为读到的字节数，若已到达文件尾端则返回0；若出错，返回-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">ssize_t</span> <span class="title">pread</span><span class="params">(<span class="keyword">int</span> fd, <span class="keyword">void</span> *buf, <span class="keyword">size_t</span> nbytes, <span class="keyword">off_t</span> offset)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回已写的字节数；若出错，返回-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">ssize_t</span> <span class="title">pwrite</span><span class="params">(<span class="keyword">int</span> fd, <span class="keyword">const</span> <span class="keyword">void</span> *buf, <span class="keyword">size_t</span> bytes, <span class="keyword">off_t</span> offset)</span></span>;</span><br></pre></td></tr></table></figure><p>调用pread相当于调用lseek后调用read，但pread是一个原子操作，即：</p><ul><li>调用pread时，无法中断其定位和读操作。</li><li>不更新当前文件的偏移量。</li></ul><p>调用pwrite相当于调用lseek后调用write，但同样，是一个原子操作。</p><p>另一个原子操作的例子：判断一个文件是否存在，若存在，则open失败；若不存在，则创建该文件。open提供了oflag的O_EXCL和O_CREAT，使得这两个操作成为一个原子操作。如果没有O_EXCL，则可能要这样写代码：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">if</span> ((fd = open(pathname, O_WRONLY)) &lt; <span class="number">0</span>)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">if</span> (errno == ENOENT)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> ((fd = creat(path, mode)) &lt; <span class="number">0</span>)</span><br><span class="line">            err_sys(<span class="string">&quot;creat error&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">    &#123;</span><br><span class="line">        err_sys(<span class="string">&quot;open error&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上述代码带来的问题是，如果在open和creat之间，另一个进程创建了该文件，就会出现问题。</p><p>一般而言，<strong>原子操作</strong>（atomic operation）指的是由多步组成的一个操作。如果该操作原子的执行，则<strong>要么执行完所有步骤，要么一步也不执行，不可能只执行所有步骤的一个子集</strong>。</p><h3 id="3-12-函数dup和dup2"><a href="#3-12-函数dup和dup2" class="headerlink" title="3.12 函数dup和dup2"></a>3.12 函数dup和dup2</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">dup</span><span class="params">(<span class="keyword">int</span> fd)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">dup2</span><span class="params">(<span class="keyword">int</span> fd, <span class="keyword">int</span> fd2)</span></span></span><br></pre></td></tr></table></figure><p>上面两个函数都可用来复制一个现有的文件描述符。若成功，返回新的文件描述符；若出错，返回-1。</p><p>由dup返回的新文件描述符一定是当前可用文件描述符中的最小值。对于dup2，可以用fd2参数指定新描述符的值。如果fd2已经打开，则先将其关闭；如果fd等于fd2，则dup2返回fd2，而不关闭它。否则，fd2的FD_CLOEXEC文件描述符标志就被清除，这样fd2在进程调用exec时是打开状态。</p><p>这些函数返回的新文件描述符与参数fd共享一个文件表项。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210511100140948.png" alt="image-20210511100140948"></p><p>每个文件描述符都有它自己的一套文件描述符标志。</p><h3 id="3-13-函数-sync、fsync-和-fdatasync"><a href="#3-13-函数-sync、fsync-和-fdatasync" class="headerlink" title="3.13 函数 sync、fsync 和 fdatasync"></a>3.13 函数 sync、fsync 和 fdatasync</h3><p>延迟写：传统的Unix系统实现在内核中设有缓冲区高速缓存或页高速缓存，大多数磁盘I/O都通过缓冲区进行。<strong>当我们向文件写入数据时，内核通常先将数据复制到缓冲区中，然后排入队列，晚些时候再写入磁盘</strong>。通常，当内核需要重用缓冲区来存放其他磁盘块数据时，它会把所有延迟写数据写入磁盘。为了保证磁盘上实际文件系统与缓冲区中内容的一致性，Unix系统提供了sync、fsync和fdatasync三个函数。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fsync</span><span class="params">(<span class="keyword">int</span> fd)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fdatasync</span><span class="params">(<span class="keyword">int</span> fd)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">sync</span><span class="params">(<span class="keyword">void</span>)</span></span>;</span><br></pre></td></tr></table></figure><p>fsync、fdatasync若成功，返回0；若出错，返回-1。</p><ul><li>sync只是<strong>将所有修改过的块缓冲区排入队列，然后就返回，它并不等待实际写磁盘操作结束</strong>。通常，称为update的系统守护进程周期性地调用（一般每隔30秒）sync函数，这就保证了定期冲洗（flush）内核的块缓冲区。命令sync(1)也调用sync函数。sync对所有文件打开的文件描述符执行。</li><li>fsync函数只是对由文件描述符fd指定的一个文件起作用，并且<strong>等待写磁盘操作结束才返回</strong>。fsync可用于数据库这样的应用程序，这种应用程序需要确保修改过的块立即写到磁盘上。</li><li>fdatasync类似于fsync，但它<strong>只影响文件的数据部分</strong>。而除数据外，<strong>fsync还会同步更新文件的属性</strong>。</li></ul><h3 id="3-14-函数fcntl"><a href="#3-14-函数fcntl" class="headerlink" title="3.14 函数fcntl"></a>3.14 函数fcntl</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;fcntl.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fcntl</span><span class="params">(<span class="keyword">int</span> fd, <span class="keyword">int</span> cmd, ... <span class="comment">/* int arg */</span>)</span></span>;</span><br></pre></td></tr></table></figure><p>fcntl函数可以改变已经打开文件的属性。若成功，则依赖cmd；若出错，返回-1。</p><p>fcntl函数有以下五种功能：</p><p>(1) 复制一个已有的描述符（cmd = F_DUPFD 或 F_DUPFD_CLOEXEC）</p><p>(2) 获取/设置文件描述符标志（cmd = F_GETFD 或 F_SETFD）</p><p>(3) 获取/设置文件状态标志（cmd = F_GETFL 或 F_SETFL）</p><p>(4) 获取/设置异步I/O所有权（cmd = F_GETOWN 或 F_SETOWN）</p><p>(5) 获取/设置记录锁 （cmd = F_GETLK、F_SETLK 或 F_SETLKW）</p><p>11种cmd中的8种：</p><ul><li><p>F_DUPFD ：复制文件描述符fd。新文件描述符作为函数值返回。它是尚未打开的各描述符中大于或等于第3个参数值（取整）中各值的最小值。新描述符与fd共享同一文件表项。但是新描述符有它自己的一套文件描述符标志。</p></li><li><p>F_DUPFD_CLOEXEC ：复制文件描述符，设置与新描述符关联的FD_CLOEXEC文件描述符标志的值，返回新文件描述符。</p></li><li><p>F_GETFD ：对应于fd的<strong>文件描述符标志</strong>作为函数值返回。当前只定义了一个文件描述符标志FD_CLOEXEC。</p></li><li><p>F_SETFD ：对于fd设置文件描述符标志。新标志值按第3个参数（取为整型值）设置。</p></li><li><p>F_GETFL ：对应于fd的<strong>文件状态标志</strong>作为函数返回值返回。文件状态标志参考3.3节open函数的第2个参数。遗憾的是，5个访问标志（O_RDONLY、O_WRONLY、O_RDWR、O_EXEC以及O_SEARCH）并不各占1位（这5个值互斥，一个文件的访问方式只能取这5个值之一）。因此首先必须用屏蔽字O_ACCMODE取得访问方式位，然后将结果与这5个值中的每一个相比较。</p></li><li><p>F_SETFL ：将文件状态标志设置为第3个参数的值（取为整型值）。可以更改的几个标志是：O_APPEND、O_NOBLOCK、O_SYNC、O_DSYNC、O_RSYNC、O_FSYNC、O_ASYNC。</p></li><li><p>F_GETOWN ：获取当前接收SIGIO和SIGURG信号的进程ID或进程组ID。</p></li><li><p>F_SETOWN ：设置接收SIGIO和SIGURG信号的进程ID或进程组ID。正的arg指定一个进程ID，负的arg表示等于arg绝对值的一个进程组ID。</p></li></ul><p>关于复制文件描述符，前面3.12节介绍了dup和dup2两个函数。实际上，执行<code>dup(fd);</code>等效于执行<code>fcntl(fd, F_DUPFD, 0);</code>，而调用<code>dup2(fd, fd2);</code>相当于执行了:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">close(fd);</span><br><span class="line">fcntl(fd, F_DUPFD, fd2);</span><br></pre></td></tr></table></figure><p>但是并不完全等效，dup2是一个原子操作，而且dup2和fcntl有一些不同的errno。</p><p>在修改文件描述符标志或文件状态标志时必须谨慎，先要获得现在的标志值，然后按照期望修改它，最后设置新标志值。不能只是执行F_SETFD或F_SETFL命令，这样会关闭以前设置的标志位。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;apue.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;fcntl.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">set_fl</span><span class="params">(<span class="keyword">int</span> fd, <span class="keyword">int</span> flags)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">/* 先获取之前的文件标志状态 */</span></span><br><span class="line">    <span class="keyword">int</span> val;</span><br><span class="line">    <span class="keyword">if</span> ((val = fcntl(fd, F_GETFL, <span class="number">0</span>)) &lt; <span class="number">0</span>)</span><br><span class="line">        err_sys(<span class="string">&quot;fcntl F_GETFL error&quot;</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/* turn on flags */</span></span><br><span class="line">    val |= flags;</span><br><span class="line">    <span class="keyword">if</span> (fcntl(fd, F_SETFL, val) &lt; <span class="number">0</span>)</span><br><span class="line">        err_sys(<span class="string">&quot;fcntl F_SETFL error&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">set_fl2</span><span class="params">(<span class="keyword">int</span> fd, <span class="keyword">int</span> flags)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">/* get previous flags */</span></span><br><span class="line">    <span class="keyword">int</span> val;</span><br><span class="line">    <span class="keyword">if</span> ((val = fcntl(fd, F_GETFL, <span class="number">0</span>)) &lt; <span class="number">0</span>)</span><br><span class="line">        err_sys(<span class="string">&quot;fcntl F_GETFL error&quot;</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/* turn off flags */</span></span><br><span class="line">    val &amp;= ~flags;</span><br><span class="line">    <span class="keyword">if</span> (fcntl(fd, F_SETFL, val) &lt; <span class="number">0</span>)</span><br><span class="line">        err_sys(<span class="string">&quot;fcntl F_SETEL error&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>打开状态用<code>val |= flags;</code>，关闭状态用<code>val &amp;= ~flags;</code>。</p><p>当使用上述函数打开O_SYNC时，write需要等待缓冲区中的数据写入磁盘块完成后返回，增加了程序执行的时间。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210511103534471.png" alt="image-20210511103534471"></p><p>在这里需要注意，设置文件状态标志位O_SYNC与sync函数的作用不是一样的，前者等待物理I/O操作完整再返回，而后者则是将缓冲块排入队列就返回，不等待物理操作完成。</p><p>O_DSYNC与O_SYNC的区别是前者不等待文件属性更新完就返回。</p><p>sync函数与fsync函数的区别是fsync只针对某个特定的文件描述符，等待物理I/O完成。</p><h3 id="3-15-函数ioctl"><a href="#3-15-函数ioctl" class="headerlink" title="3.15 函数ioctl"></a>3.15 函数ioctl</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span>         <span class="comment">/* System V */</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/ioctl.h&gt;</span>    <span class="comment">/* BSD and Linux */</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">ioctl</span><span class="params">(<span class="keyword">int</span> fd, <span class="keyword">int</span> request, ...)</span></span>;</span><br></pre></td></tr></table></figure><p>若出错，返回-1；若成功，返回其他值。ioctl函数一直是I/O操作的杂物箱。不能用本章中其他函数表示的I/O操作通常都能用ioctl表示。终端I/O是使用ioctl最多的地方。通常还要用到头文件termios.h。</p><p>嵌入式常用。</p><h3 id="3-16-dev-fd"><a href="#3-16-dev-fd" class="headerlink" title="3.16 /dev/fd"></a>3.16 /dev/fd</h3><p>较新的系统都提供名为/dev/fd的目录，其目录项是名为0、1、2等的文件。打开文件/dev/fd/n等效于复制描述符n（假定描述符n是打开的）。</p><p>在下列函数调用中：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">fd = open(<span class="string">&quot;/dev/df/0&quot;</span>, mode);</span><br></pre></td></tr></table></figure><p>大多数系统忽略它所指定的mode，而另外一些系统则要求mode必须是所引用的文件（在这里是标准输入）初始打开时所使用的打开模式的一个子集。因为上面的打开等效于：</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">fd = dup(<span class="number">0</span>);</span><br></pre></td></tr></table></figure><p>所以描述符fd和0共享同一文件表项。例如，若描述符0先前被打开为只读，那么我们也只能对fd进行读操作。即使系统忽略打开模式，而且下列调用是成功的：</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">fd = open(<span class="string">&quot;/dev/fd/0&quot;</span>, O_RDWR);</span><br></pre></td></tr></table></figure><p>我们仍然不能对fd进行写操作。</p><blockquote><p>Linux实现中的/dev/fd是个例外。它把文件描述符映射成指向底层物理文件的符号链接。例如，当打开/dev/fd/0时，事实上正在打开与标准输入关联的文件，因此返回的新文件描述符的模式与/dev/fd文件描述符的模式其实并不相关。</p></blockquote><p>某些系统提供文件/dev/stdin、/dev/stdout 和 /dev/stderr，这些等效于/dev/fd/0、/dev/fd/1和/dev/fd/2</p><p>作为命令行参数的<code>-</code>特指标准输入或输出。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">filter file2 | cat file1 - file3 | lpr</span><br></pre></td></tr></table></figure><p>cat先读file1，然后读其标准输入（即filter file2的输出），然后读file3，如果支持/dev/fd，可以删除cat对<code>-</code>的特殊处理：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">filter file2 | cat file1 /dev/fd/0 file3 | lpr</span><br></pre></td></tr></table></figure><h3 id="3-17-练习"><a href="#3-17-练习" class="headerlink" title="3.17 练习"></a>3.17 练习</h3><p>3.6 如果使用追加标志打开一个文件以便读、写，能否仍用lseek在任一位置开始读？能否用lseek更新文件中任一部分的数据？请编写一段程序验证。</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">答：可以从任一位置开始读，但不能从任一位置开始写。写需要O_WROLY或O_RDWR，单独的O_APPEND不具有写的功能。</span><br></pre></td></tr></table></figure><h2 id="第4章-文件和目录"><a href="#第4章-文件和目录" class="headerlink" title="第4章 文件和目录"></a>第4章 文件和目录</h2><h3 id="4-1-引言"><a href="#4-1-引言" class="headerlink" title="4.1 引言"></a>4.1 引言</h3><p>本章将描述文件系统的其他特征和文件的性质。</p><h3 id="4-2-函数stat、fstat、fstatat和lstat"><a href="#4-2-函数stat、fstat、fstatat和lstat" class="headerlink" title="4.2 函数stat、fstat、fstatat和lstat"></a>4.2 函数stat、fstat、fstatat和lstat</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/types.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/stat.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">stat</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *pathname, struct stat *statbuf)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fstat</span><span class="params">(<span class="keyword">int</span> fd, struct stat *statbuf)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">lstat</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *pathname, struct stat *statbuf)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;fcntl.h&gt;</span>           <span class="comment">/* Definition of AT_* constants */</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/stat.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fstatat</span><span class="params">(<span class="keyword">int</span> dirfd, <span class="keyword">const</span> <span class="keyword">char</span> *pathname, struct stat *statbuf, <span class="keyword">int</span> flags)</span></span>;</span><br></pre></td></tr></table></figure><p>这四个函数在函数执行成功时返回0，出错时返回-1。</p><ul><li><p>一旦给出pathname，stat函数将返回与此命名文件有关的信息结构。</p></li><li><p>fstat函数获得已在文件描述符fd上打开文件的有关信息。</p></li><li><p>lstat类似于stat，但是命名的文件是一个符号链接时，lstat返回该符号链接的有关信息，而不是由该符号链接引用的文件信息。</p></li><li><p>fstatat函数为一个相对于当前打开目录（由dirfd参数指向）的路径名返回文件统计信息。flags参数控制着是否跟随着一个符号链接。当<code>AT_SYMLINK_NOFOLLOW</code>标志被设置时，fstatat不会跟随符号链接，而是返回符号链接本身的信息。否则，在默认情况下，返回的是符号链接所指向的实际文件信息。如果fd被设置为<code>AT_FDCWD</code>，并且pathname参数是一个相对路径名，fstatat会计算相对于当前目录的pathname参数。如果pathname是一个绝对路径，fd参数就会被忽略。在这两种情况下，根据flag 的取值，fstatat的作用就跟stat或lstat一样。</p></li></ul><p>参数buf是一个指针，指向的结构为stat，定义如下：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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="class"><span class="keyword">struct</span> <span class="title">stat</span> &#123;</span></span><br><span class="line">    <span class="keyword">dev_t</span>     st_dev;         <span class="comment">/* ID of device containing file */</span></span><br><span class="line">    <span class="keyword">ino_t</span>     st_ino;         <span class="comment">/* Inode number */</span></span><br><span class="line">    <span class="keyword">mode_t</span>    st_mode;        <span class="comment">/* File type and mode (permission) */</span></span><br><span class="line">    <span class="keyword">nlink_t</span>   st_nlink;       <span class="comment">/* Number of hard links */</span></span><br><span class="line">    <span class="keyword">uid_t</span>     st_uid;         <span class="comment">/* User ID of owner */</span></span><br><span class="line">    <span class="keyword">gid_t</span>     st_gid;         <span class="comment">/* Group ID of owner */</span></span><br><span class="line">    <span class="keyword">dev_t</span>     st_rdev;        <span class="comment">/* Device ID (if special file) */</span></span><br><span class="line">    <span class="keyword">off_t</span>     st_size;        <span class="comment">/* Total size, in bytes */</span></span><br><span class="line">    <span class="keyword">blksize_t</span> st_blksize;     <span class="comment">/* Block size for filesystem I/O */</span></span><br><span class="line">    <span class="keyword">blkcnt_t</span>  st_blocks;      <span class="comment">/* Number of 512B blocks allocated */</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">/* Since Linux 2.6, the kernel supports nanosecond</span></span><br><span class="line"><span class="comment">       precision for the following timestamp fields.</span></span><br><span class="line"><span class="comment">       For the details before Linux 2.6, see NOTES. */</span></span><br><span class="line"></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">timespec</span> <span class="title">st_atim</span>;</span>  <span class="comment">/* Time of last access */</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">timespec</span> <span class="title">st_mtim</span>;</span>  <span class="comment">/* Time of last modification */</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">timespec</span> <span class="title">st_ctim</span>;</span>  <span class="comment">/* Time of last status change */</span></span><br><span class="line"></span><br><span class="line">    <span class="meta">#<span class="meta-keyword">define</span> st_atime st_atim.tv_sec      <span class="comment">/* Backward compatibility */</span></span></span><br><span class="line">    <span class="meta">#<span class="meta-keyword">define</span> st_mtime st_mtim.tv_sec</span></span><br><span class="line">    <span class="meta">#<span class="meta-keyword">define</span> st_ctime st_ctim.tv_sec</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>stat函数用的最多的地方可能就是<code>ls -l</code>命令。</p><h3 id="4-3-文件类型"><a href="#4-3-文件类型" class="headerlink" title="4.3 文件类型"></a>4.3 文件类型</h3><p>文件类型包括如下几种：</p><ul><li>普通文件（regular file）</li><li>目录文件（directory file）：这种文件包含了其他文件的名字以及指向与这些文件有关信息的指针。对一个目录文件具有读权限的任一进程都可以读该目录的内容，但只有<strong>内核</strong>可以直接写目录文件。</li><li>块特殊文件（block special file）：提供对设备（如磁盘）带缓冲的访问，每次访问以固定长度为单位进行。</li><li>字符特殊设备（character special file）：提供对设备的不带缓冲访问，每次访问长度可变。<strong>系统中的所有设备要么是字符特殊文件，要么是块特殊设备</strong>。</li><li>FIFO：用于进程间通信，有时也称为命名管道（named pipe）。</li><li>套接字（socket）：用于进程间的网络通信。套接字也可用于在一台宿主机上进程之间的非网络通信。</li><li>符号链接（symbolic link）：指向另一个文件。</li></ul><p><em>文件类型信息</em>包含在stat结构中的st_mode成员中。下列宏可以用于st_mode类型的参数：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210512092957111.png" alt="image-20210512092957111" style="zoom:67%;" /></p><p>下列宏可以用于stat结构来确定IPC（进程间通信）对象：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210512093045164.png" alt="image-20210512093045164" style="zoom:67%;" /></p><h3 id="4-4-设置用户ID和设置组ID"><a href="#4-4-设置用户ID和设置组ID" class="headerlink" title="4.4 设置用户ID和设置组ID"></a>4.4 设置用户ID和设置组ID</h3><p>与一个进程相关联的ID有6个或更多，如下图所示：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210512094448990.png" alt="image-20210512094448990" style="zoom: 80%;" /></p><ul><li>实际用户ID和实际组ID标识我们究竟是谁。这两个字段在登录时取自口令文件中的登录项。通常，在一个登录会话期间这些值并不改变，但是超级用户进程有方法改变它们。<span style="color: red">个人理解为启动该进程的用户ID、组ID就是实际用户ID和实际组ID。</span></li><li>有效用户ID、有效组ID以及附属组ID决定了我们的文件访问权限。</li><li>保存的设置用户ID和保存的设置组ID在执行一个程序时包含了有效用户ID和有效组ID的副本。<span style="color: red">设置用户ID和设置组ID是两个特殊的权限位，用于为启动某进程的用户或用户组设置在运行时与文件所属用户和用户组所拥有的权限，对应文件权限标志<code>s</code>。</span></li></ul><p><span style="color: blue">通常，有效用户ID等于实际用户ID，有效组ID等于实际组ID。</span></p><p>每个文件有一个<strong>所有者</strong>和<strong>组所有者</strong>，所有者由stat结构中的st_uid指定，组所有者由st_gid指定。</p><p>文件模式字中的特殊两位：设置用户ID（set-user-ID）位和设置组ID（set-group-ID）位。</p><ul><li>设置用户ID（set-user-ID）位：当执行此文件时，将进程的有效用户ID设置为文件所有者的用户ID（st_uid）。</li><li>设置用户组ID（set-group-ID）位：当执行此文件时，将进程的有效用户ID设置为文件的组所有者ID（st_gid）。</li></ul><p>例如，若文件的所有者是超级用户，而且设置了该文件的设置用户ID位，那么当该程序文件由一个进程执行，该进程具有超级用户权限，不管执行此文件的进程的实际用户ID是什么。</p><p>一个最简单的例子，用户可以使用passwd命令修改自己的密码。该程序是一个设置用户ID程序，因为该程序应能将用户的新口令写入文件中（一般是/etc/passwd或/etc/shadow），该文件的所有这为root，且这个文件的权限一般为<code>-rw-r--r--</code>，其他用户没有写的权限，所以需要使用设置用户ID功能才能是普通用户使用passwd命令改密码。</p><p><span style="color: red">设置用户ID位及设置组ID位都包含在文件st_mode值中，分别使用S_ISUID和S_ISGID进行<strong>位与</strong>测试可以检测出是否有对应权限。</span></p><h3 id="4-5-文件访问权限"><a href="#4-5-文件访问权限" class="headerlink" title="4.5 文件访问权限"></a>4.5 文件访问权限</h3><p>st_mode值也包含了对文件的访问权限位。每个文件有9个访问权限位，可将它们分成3类：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210512095237126.png" alt="image-20210512095237126"></p><p>用户是指文件所有者（owner），组指文件所有组(group)，其他指其他用户（other）。chmod命令可以修改这9个权限位。</p><ul><li><p>我们用名字打开任一类型的文件时，对该名字中包含的每一个目录，包括它可能隐含的当前工作目录都应具有<strong>执行权限</strong>。这就是为什么对于目录执行权限位常被称为搜索位的原因。</p><p>注意，对于目录的读权限和执行权限的意义是不同的。读权限允许我们读目录，获得在该目录中所有文件名的列表。当一个目录是我们要访问文件的路径名的一个组成部分时，对该目录的<strong>执行权限</strong>使我们可以<strong>通过</strong>该目录。</p></li><li><p>对于一个文件的<strong>读权限</strong>决定了我们是否能打开现有文件进行<strong>读操作</strong>。这与open函数的O_RDONLY和O_RDWR标志相关。</p></li><li><p>对于一个文件的<strong>写权限</strong>决定了我们是否能打开现有文件进行<strong>写操作</strong>。这与open函数的O_WRONLY和O_RDWR标志相关。</p></li><li><p>为了在open函数中对一个文件指定O_TRUNC标志，必须对该文件具有<strong>写权限</strong>。</p></li><li><p>为了在一个目录中创建一个新文件，必须对该目录具有<strong>写权限</strong>和<strong>执行权限</strong>。</p></li><li><p>为了删除一个现有文件，必须对<strong>包含该文件的目录</strong>具有<strong>写权限</strong>和<strong>执行权限</strong>。对该文件本身则<strong>不需要</strong>有读、写权限。</p></li><li><p>如果用7个exec函数中的任何一个执行某个文件，都必须对该文件具有<strong>执行权限</strong>。该文件还必须是一个<strong>普通文件</strong>。</p></li></ul><p>文件所有者（st_uid）和（st_gid）是文件的性质，两个有效ID（有效用户ID和有效组ID）和附属组ID是进程的性质。</p><p>内核进行的测试具体如下：</p><ul><li>若进程的<strong>有效用户ID</strong>是0（超级用户），则允许访问。这给予了超级用户对整个文件系统进行处理的最充分的自由。</li><li>若进程的<strong>有效用户ID</strong>等于文件的所有者ID（也就是进程拥有此文件），那么如果所有者适当的访问权限被设置，则允许访问；否则拒绝访问。</li><li>若进程的<strong>有效组ID</strong>或进程的附属组ID之一等于文件的组ID，那么如果组适当的访问权限被设置，则允许访问；否则拒绝访问。</li><li>若其他用户适当的访问权限位被设置，则允许访问；否则拒绝访问。</li></ul><h3 id="4-6-新文件和目录的所有权"><a href="#4-6-新文件和目录的所有权" class="headerlink" title="4.6 新文件和目录的所有权"></a>4.6 新文件和目录的所有权</h3><p>新文件的用户ID设置为进程的有效用户ID。关于组ID，POSIX.1允许实现选择下列之一作为新文件的组ID：</p><ol><li>新文件的组ID可以是进程的有效组ID</li><li>新文件的组ID可以是它所在目录的组ID</li></ol><h3 id="4-7-函数access和faccessat"><a href="#4-7-函数access和faccessat" class="headerlink" title="4.7 函数access和faccessat"></a>4.7 函数access和faccessat</h3><p>access和faccessat函数是按<strong>实际用户ID</strong>和<strong>实际组ID</strong>进行访问权限测试的。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">access</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *pathname, <span class="keyword">int</span> mode)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;fcntl.h&gt;</span>           <span class="comment">/* Definition of AT_* constants */</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">faccessat</span><span class="params">(<span class="keyword">int</span> dirfd, <span class="keyword">const</span> <span class="keyword">char</span> *pathname, <span class="keyword">int</span> mode, <span class="keyword">int</span> flags)</span></span>;</span><br></pre></td></tr></table></figure><p>两个函数若执行成功，返回值为0，失败返回-1。</p><p>即使一个进程可能已经通过设置用户ID或设置组ID以超级用户权限运行，它仍可能想验证其实际用户能否访问一个给定的文件。</p><p>测试文件是否已经存在，mode就为<code>F_OK</code>；否则，mode是图4-7中所列常量的按位或。</p><div class="table-container"><table><thead><tr><th>mode</th><th>说明</th></tr></thead><tbody><tr><td>R_OK</td><td>测试读权限</td></tr><tr><td>W_OK</td><td>测试写权限</td></tr><tr><td>X_OK</td><td>测试执行权限</td></tr></tbody></table></div><p>至于faccessat函数的fd参数和pathname参数，其用法和openat函数的fd参数和pathname一致。</p><p>flag参数可以用于改变faccessat的行为，如果flag设置为<code>AT_EACCESS</code>，访问检查用的是调用进程的有效用户ID和有效组ID，而不是实际用户ID和实际组ID。</p><h3 id="4-8-函数umask"><a href="#4-8-函数umask" class="headerlink" title="4.8 函数umask"></a>4.8 函数umask</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/stat.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">mode_t</span> <span class="title">umask</span><span class="params">(<span class="keyword">mode_t</span> cmask)</span></span>;</span><br></pre></td></tr></table></figure><p>umask函数为进程设置文件模式<strong>创建屏蔽字</strong>，并<strong>返回之前的值</strong>。该函数是少数几个没有出错返回函数中的一个。参数cmask是由图4-6中列出的9个常量（S_IRUSR、S_IWUSR等）中的若干按位“或”构成的。在文件模式创建屏蔽字中为1的位，在文件mode中的相应位一定被关闭。</p><p>Unix系统的大多数用户从不处理他们的umask值。通常在登录时，由shell的启动文件设置一次，然后不再改变。尽管如此，当编写创建新文件的程序时，如果我们想确保指定的访问权限位已经激活，那么必须在进程运行时修改umask值。</p><p><strong>umask命令</strong></p><p>umask命令用于查看或显示被屏蔽的位的八进制或许可的权限，还可用于设置屏蔽位，以8进制表示。</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="built_in">umask</span></span><br><span class="line">022</span><br><span class="line">$ <span class="built_in">umask</span> -S</span><br><span class="line">u=rwx, g=rx, o=rx</span><br><span class="line">$ <span class="built_in">umask</span> 027</span><br><span class="line">$ <span class="built_in">umask</span> -S</span><br><span class="line">u=rwx, g=rx, o=</span><br></pre></td></tr></table></figure><h3 id="4-9-函数chmod、fchmod和fchmodat"><a href="#4-9-函数chmod、fchmod和fchmodat" class="headerlink" title="4.9 函数chmod、fchmod和fchmodat"></a>4.9 函数chmod、fchmod和fchmodat</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/stat.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">chmod</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *pathname, <span class="keyword">mode_t</span> mode)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fchmod</span><span class="params">(<span class="keyword">int</span> fd, <span class="keyword">mode_t</span> mode)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fchmodat</span><span class="params">(<span class="keyword">int</span> fd, <span class="keyword">const</span> <span class="keyword">char</span> *pathname, <span class="keyword">mode_t</span> mode, <span class="keyword">int</span> flag)</span></span>;</span><br></pre></td></tr></table></figure><p>chmod、fchmod和fchmodat这3个函数使我们可以更改现有文件的访问权限。若执行成功，则返回0，执行失败则返回-1。flag参数可以用于改变fchmodat的行为，当设置了<code>AT_SYMLINK_NOFOLLOW</code>标志时，fchmodat并不会跟随符号链接。</p><p>为了改变一个文件的权限位，<strong>进程的有效用户ID必须等于文件的所有者ID，或该进程必须具有超级用户权限</strong>。参数mode时图4-11中所示常量的按位或。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210512123036645.png" alt="image-20210512123036645"></p><p>chmod函数更新的只是i节点最近一次被更改的时间。按系统默认方式，<code>ls -l</code>列出的是最后修改文件内容的时间。</p><p>chmod函数在下列条件下自动清除两个权限位：</p><ul><li>Solaris等系统对用于普通文件的粘着位赋予了特殊含义，在这些系统上如果我们试图设置普通文件粘着位（S_ISVTX），而且又没有超级用户权限，那么mode中的粘着位自动被关闭。这意味着只有超级用户才能设置文件的粘着位。这样做的理由是防止恶意用户设置粘着位，由此影响系统性能。</li><li>新创建文件的组ID可能不是调用进程所属的组。新文件的组ID可能是父目录的组ID。特别地，如果新文件的组ID不等于进程的有效组ID或者进程附属组ID中的一个，而且进程没有超级用户权限，那么设置ID位会被自动关闭。这就防止了用户创建一个设置组ID文件，而该文件是由非该用户所属的组拥有的。</li></ul><h3 id="4-10-粘着位"><a href="#4-10-粘着位" class="headerlink" title="4.10 粘着位"></a>4.10 粘着位</h3><p>S_ISVTX。</p><h3 id="4-11-函数chown、fchown、fchownat和lchown"><a href="#4-11-函数chown、fchown、fchownat和lchown" class="headerlink" title="4.11 函数chown、fchown、fchownat和lchown"></a>4.11 函数chown、fchown、fchownat和lchown</h3><p>chown函数可用于更改文件的用户ID和组ID。如果两个参数owner或group中的任意一个是-1，则对应的ID不变。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">chown</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *pathname, <span class="keyword">uid_t</span> owner, <span class="keyword">gid_t</span> group)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fchown</span><span class="params">(<span class="keyword">int</span> fd, <span class="keyword">uid_t</span> owner, <span class="keyword">gid_t</span> group)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fchownat</span><span class="params">(<span class="keyword">int</span> fd, <span class="keyword">const</span> <span class="keyword">char</span> *pathname, <span class="keyword">uid_t</span> owner, <span class="keyword">gid_t</span> group, <span class="keyword">int</span> flag)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">lchown</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *pathname, <span class="keyword">uid_t</span> owner, <span class="keyword">gid_t</span> group)</span></span>;</span><br></pre></td></tr></table></figure><p>四个函数的返回值：若成功，返回0；若出错，返回-1。</p><p>除了所引用的文件是符号链接外，这四个函数的操作类似。在符号链接情况下，lchown和fchownat（设置了<code>AT_SYMLINK_NOFOLLOW</code>标志）更改符号链接本身的所有者，而不是符号链接所指向的文件所有者。</p><p>超级用户可以更改文件的所有者和所有组，而有些系统支持任一用户更改他们所拥有的文件所有者。</p><h3 id="4-12-文件长度"><a href="#4-12-文件长度" class="headerlink" title="4.12 文件长度"></a>4.12 文件长度</h3><p>stat结构成员st_size表示以字节为单位的文件的长度。此字段只对普通文件、目录文件和符号链接有意义。</p><p>对于普通文件，其文件长度可以是0，在开始读这种文件时，将得到文件结束符（EOF）。对于目录，文件长度通常是一个数（如16或512）的整数倍。对于符号链接，文件长度是指向的文件名中的实际字节数。</p><p>现今，大多数现代的UNIX系统提供字段st_blksize和st_blocks。前者是对文件I/O较合适的块长度，后者是所分配的实际512字节块块数。</p><h4 id="文件中的空洞"><a href="#文件中的空洞" class="headerlink" title="文件中的空洞"></a>文件中的空洞</h4><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210512132044857.png" alt="image-20210512132044857"></p><p>文件core的长度稍稍超过8MB，可是du命令报告该文件所使用的磁盘空间总量是272个512字节块（即139264字节）。很明显，此文件中有很多空洞。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210512132255994.png" alt="image-20210512132255994"></p><p>使用wc命令，可以看出正常的I/O操作读整个文件的长度。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210512132333732.png" alt="image-20210512132333732"></p><p>如果使用cat复制这个文件，那么所有的这些空洞都会被填满，其中所有实际数据字节皆填写为0。</p><h3 id="4-13-文件截断"><a href="#4-13-文件截断" class="headerlink" title="4.13 文件截断"></a>4.13 文件截断</h3><p>在打开文件时使用O_TRUNC标志截断为0是一个特例。为了截断文件，可以使用下列函数：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">truncate</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span>* pathname, <span class="keyword">off_t</span> length)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">ftruncate</span><span class="params">(<span class="keyword">int</span> fd, <span class="keyword">off_t</span> length)</span></span>;</span><br></pre></td></tr></table></figure><p>若成功，返回0；若失败，返回-1。</p><p>这两个函数将现有文件截断为length。若原来的文件长度大于length，则截断后大于length的部分无法访问。若原来问价你的长度小于length，则截断后文件长度将增加，在以前的文件尾端和新的文件尾端之间的数据将读作0（也就是可能在文件中创建了一个空洞）。</p><h3 id="4-14-文件系统"><a href="#4-14-文件系统" class="headerlink" title="4.14 文件系统"></a>4.14 文件系统</h3><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210512141454390.png" alt="image-20210512141454390"></p><p>我们可以把一个磁盘分成一个或多个分区。每个分区可以包含一个文件系统。i节点时固定长度的记录项，它包含有关文件的大部分信息。更仔细地观察一个柱面的i节点和数据块部分，可以看到下图所示情况：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210512141628360.png" alt="image-20210512141628360"></p><ul><li><p>上图中，有两个目录项指向同一个i节点。每个i节点中都有一个链接计数，其值是指向该i节点的目录项数。只有当链接计数减少至0时，才可删除该文件（也可以释放该文件占用的数据块）。这就是为什么“解除一个文件的链接”操作并不总是意味着“释放该文件占用的磁盘块”的原因。这也是为什么删除一个目录项的函数被称为unlink而不是delete的原因。在stat结构中，链接计数包含在st_nlink成员中，其基本系统数据类型是nlink_t，这种链接类型称为<strong>硬链接</strong>。</p></li><li><p>另一种链接类型称为<strong>符号链接</strong>。符号链接文件的实际内容包含了该链接所指向的文件的名字。符号链接i节点中文件类型是S_IFLNK，于是系统知道这是一个符号链接。</p></li><li>i节点包含了文件有关的所有信息：文件类型、文件访问权限位、文件长度和指向文件数据块的指针等。stat结构中的大多数信息都取自i节点。只有两项重要数据存放在目录项中：文件名和i节点编号。i节点编号的数据类型是ino_t。</li><li>因为目录项中的i节点编号指向同一文件系统中的相应i节点，一个目录项不能指向另一个文件系统的i节点。因此ln命令不能跨越文件系统。</li><li>当在不更换文件系统的情况下为一个文件重命名时，该文件的实际内容并未移动，只需构造一个指向现有i节点的新目录项，并删除老的目录项。链接计数不会改变。这就是mv命令的通常操作方式。</li></ul><p>上述时普通文件的链接计数的概念，对于目录文件的链接计数字段如下所示：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210512143539155.png" alt="image-20210512143539155"></p><p>我们在当前目录下创建一个testdir目录。testdir目录的i节点的链接计数项为2，一个来自创建该目录的父目录，即当前工作目录，还有一个来自于testdir目录中的<code>.</code>。而当前工作目录的i节点的链接计数项至少为3，一个来自于其父目录，一个来自于当前目录下的<code>.</code>，还有一个来自testdir目录的<code>..</code>。注意，在父目录中每一个子目录都使该父目录的链接计数增加1。</p><h3 id="4-15-函数link、linkat、unlink、unlinkat和remove"><a href="#4-15-函数link、linkat、unlink、unlinkat和remove" class="headerlink" title="4.15 函数link、linkat、unlink、unlinkat和remove"></a>4.15 函数link、linkat、unlink、unlinkat和remove</h3><p>创建一个指向现有文件的链接的方法是使用link函数或linkat函数。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">link</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span>* existingpath, <span class="keyword">const</span> <span class="keyword">char</span> *newpath)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">linkat</span><span class="params">(<span class="keyword">int</span> efd, <span class="keyword">const</span> <span class="keyword">char</span> *existingpath, <span class="keyword">int</span> nfd, <span class="keyword">const</span> <span class="keyword">char</span> *newpath, <span class="keyword">int</span> flag)</span></span>;</span><br></pre></td></tr></table></figure><p>若成功，返回0；若出错，返回-1。</p><p>这两个函数创建一个新目录项newpath，它引用现有文件existingpath。如果newpath已经存在，则返回出错。只创建newpath中最后一个分量，路径中的其他部分应当已经存在。</p><p>linkat函数使用flag参数控制指向现有符号链接还是指向现有符号链接所指向的文件。（flag = AT_SYMLINK_FOLLOW说明指向符号链接指向的文件）。</p><p>为删除一个现有的目录项，可以调用unlink函数：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">unlink</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *pathname)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">unlinkat</span><span class="params">(<span class="keyword">int</span> fd, <span class="keyword">const</span> <span class="keyword">char</span> *pathname, <span class="keyword">int</span> flag)</span></span>;</span><br></pre></td></tr></table></figure><p>若成功，返回0；若出错，返回-1。</p><p>这两个函数删除目录项，并将由pathname所引用的文件的链接计数减1。如果对该文件还有其他链接，则仍可通过其他链接访问该文件的数据。如果出错，则不对该文件做任何更改。</p><p>只有当链接计数达到0时，该文件的内容才可被删除。另一个条件也会阻止删除文件的内容——只要有进程打开了文件，其内容也不能删除。关闭一个文件时，内核首先检查打开该文件的进程个数，如果这个计数达到0，内核再去检查其链接计数；如果计数也是0，那么就删除该文件的内容。</p><p>unlinkat的flag参数给出一种方法，使调用进程可以改变unlinkat函数的默认行为。当<code>AT_REMOVEDIR</code>标志被设置时，unlinkat函数可以类似于rmdir一样删除目录。如果这个标志被清除，unlinkat与unlink执行同样的操作。</p><p>我们也可以使用remove函数解除对一个文件或目录的链接。对于文件，remove功能与unlink相同。对于目录，remove的功能与rmdir相同。</p><figure class="highlight c"><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">remove</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *pathname)</span></span>;</span><br></pre></td></tr></table></figure><p>若成功，返回0；若出错，返回-1。</p><h3 id="4-16-函数rename和renameat"><a href="#4-16-函数rename和renameat" class="headerlink" title="4.16 函数rename和renameat"></a>4.16 函数rename和renameat</h3><p>文件或目录可以用rename函数或renameat函数进行重命名。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">rename</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *oldname, <span class="keyword">const</span> <span class="keyword">char</span> *newname)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">renameat</span><span class="params">(<span class="keyword">int</span> oldfd, <span class="keyword">const</span> <span class="keyword">char</span> *oldname, <span class="keyword">int</span> newfd, <span class="keyword">const</span> <span class="keyword">char</span> *newname)</span></span>;</span><br></pre></td></tr></table></figure><p>若成功，返回0；若出错，返回-1。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210512152722143.png" alt="image-20210512152722143"></p><h3 id="4-17-符号链接"><a href="#4-17-符号链接" class="headerlink" title="4.17 符号链接"></a>4.17 符号链接</h3><p>硬链接直接指向文件的i节点，且有以下限制：</p><ul><li>硬链接通常要求链接和文件位于同一文件系统中</li><li>只有超级用户才能创建指向目录的硬链接</li></ul><p>符号链接以及它指向的何种对象无文件系统的限制，任何用户都可以创建指向目录的符号链接。符号链接一般用于将一个文件或整个目录结构移到系统中的另一位置。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210514150353762.png" alt="image-20210514150353762" style="zoom: 67%;" /></p><p>使用符号链接可能在文件系统中引入循环。大多数查找路径名的函数在这种情况发生时都将返回出错，errno值为ELOOP。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210514151153998.png" alt="image-20210514151153998"></p><p>这样的循环很容易消除。因为unlink并不跟随符号链接，所以可以unlink文件foo/testdir。但是如果创建了一个构成这种循环的硬链接，那么就很难消除它。</p><p><code>ls</code>命令使用<code>-l</code>参数可以查看文件的较详细信息，包括符号链接，使用<code>-F</code>参数会在符号链接后加上<code>@</code>。</p><h3 id="4-18-创建和读取符号链接"><a href="#4-18-创建和读取符号链接" class="headerlink" title="4.18 创建和读取符号链接"></a>4.18 创建和读取符号链接</h3><p>可以使用symlink或symlinkat函数创建一个符号链接。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">symlink</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *actualpath, <span class="keyword">const</span> <span class="keyword">char</span> *sympath)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">symlinkat</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *actualpath, <span class="keyword">int</span> fd, <span class="keyword">const</span> <span class="keyword">char</span> *sympath)</span></span>;</span><br></pre></td></tr></table></figure><p>若成功，返回0；若出错，返回-1。函数创建一个指向actualpath的新目录项sympath。<span style="color: red">在创建此符号链接时，并不要求actualpath已经存在。</span>并且，actualpath和sympath并不需要位于同一文件系统中。</p><p>symlinkat函数中的fd参数为已打开的文件描述符，使得sympath的路径相对于该文件描述符。如果sympath为绝对路径或fd设置为<code>AT_FDCWD</code>，则symlinkat与symlink等同。</p><p>由于open函数跟随符号链接，所以需要一种方法打开链接本身，并读该链接中的名字。readlink和readlinkat函数提供了这种功能：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">ssize_t</span> <span class="title">readlink</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *pathname, <span class="keyword">char</span> *buf, <span class="keyword">size_t</span> bufsize)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">ssize_t</span> <span class="title">readlinkat</span><span class="params">(<span class="keyword">int</span> fd, <span class="keyword">const</span> <span class="keyword">char</span> *pathname, <span class="keyword">char</span> *buf, <span class="keyword">size_t</span> bufsize)</span></span>;</span><br></pre></td></tr></table></figure><p>两个函数组合了open、read和close的所有操作。如果函数成功，则返回读入buf的字节数。出错返回-1。在buf中返回的符号链接的内容<strong>不以null字节终止</strong>。</p><h3 id="4-19-文件的时间"><a href="#4-19-文件的时间" class="headerlink" title="4.19 文件的时间"></a>4.19 文件的时间</h3><div class="table-container"><table><thead><tr><th>字段</th><th>说明</th><th>例子</th><th>ls(1)选项</th></tr></thead><tbody><tr><td>st_atime</td><td>文件数据的最后访问时间</td><td>read</td><td>-u</td></tr><tr><td>st_mtime</td><td>文件数据的最后修改时间</td><td>write</td><td>默认</td></tr><tr><td>st_ctime</td><td>i节点状态的最后更改时间</td><td>chmod、chown</td><td>-c</td></tr></tbody></table></div><p>注意，修改时间（st_mtime）和状态更改时间（st_ctime）之间的区别。修改时间时文件内容最后一次被修改的时间。状态更改时间是该文件的i节点最后一次被修改的时间。更改文件的访问权限、更改用户ID、更改连接数等都将影响到i节点，但这些操作并没有修改文件的内容。文件的内容与文件的状态信息是分开存访的，所以，除了要记录文件数据修改时间外，还需要记录状态更改时间。</p><p>注意，系统并不维护对一个i节点的最后一次访问时间，所以access和stat函数并不更改上述3个时间中的任意一个。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210514194111564.png" alt="image-20210514194111564"></p><h3 id="4-20-函数futimens、utimensat和utimes"><a href="#4-20-函数futimens、utimensat和utimes" class="headerlink" title="4.20 函数futimens、utimensat和utimes"></a>4.20 函数futimens、utimensat和utimes</h3><p>一个文件的<strong>访问</strong>和<strong>修改</strong>时间可以用以下几个函数更改。futimens和utimensat函数可以指定纳秒级精度的时间戳。用到的数据结构是与stat函数族相同的timespec结构。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/stat.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">futimens</span><span class="params">(<span class="keyword">int</span> fd, <span class="keyword">const</span> struct timespec times[<span class="number">2</span>])</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">utimensat</span><span class="params">(<span class="keyword">int</span> fd, <span class="keyword">const</span> <span class="keyword">char</span> *path, <span class="keyword">const</span> struct timespec times[<span class="number">2</span>], <span class="keyword">int</span> flag)</span></span>;</span><br></pre></td></tr></table></figure><p>若成功，返回0；否则，返回-1。times数组第一个元素包含访问时间，第二个元素包含修改时间。这两个时间是日历时间，即从（1970年1月1日00:00:00）以来所经过的秒数。不足秒的部分用纳秒表示。</p><p>时间戳可以按下列4中方式之一进行指定：</p><ul><li>如果times参数是一个空指针，则访问时间和修改时间两者都设置成当前时间。</li><li>如果times参数指向两个timespce结构的数组，任一数组元素的tv_nsec字段的值为<code>UTIME_NOW</code>，相应的时间戳就设置为当前时间，忽略相应的tv_sec字段。</li><li>如果times参数指向两个timespec结构的数组，任一数组元素的tv_nsec字段的值为<code>UTIME_OMIT</code>，相应的时间戳保持不变，忽略相应的tv_sec字段。</li><li>如果times参数指向两个timespec结构的数组，且tv_nsec字段的值为既不是<code>UTIME_NOW</code>也不是<code>UTIME_OMIT</code>，在这种情况下，相应的时间戳设置为相应的tv_sec和tv_nsec字段的值。</li></ul><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">timespec</span> &#123;</span></span><br><span class="line">  <span class="keyword">time_t</span> tv_sec;  <span class="comment">/* seconds */</span></span><br><span class="line">    <span class="keyword">long</span>   tv_nsec; <span class="comment">/* nanoseconds */</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>utimensat默认行为是跟随符号链接，如果将flag参数设置为<code>AT_SYMLINK_NOFOLLOW</code>标志，则符号链接本身的时间就会被修改（如果路径名是符号链接）。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/time.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">utimes</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *pathname, <span class="keyword">const</span> struct timeval times[<span class="number">2</span>])</span></span>;</span><br></pre></td></tr></table></figure><p>utimes函数对路径名进行操作。timeval结构如下：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="class"><span class="keyword">struct</span> <span class="title">timeval</span> &#123;</span></span><br><span class="line">    <span class="keyword">time_t</span> tv_sec;  <span class="comment">/* seconds */</span></span><br><span class="line">    <span class="keyword">long</span>   tv_usec; <span class="comment">/* microsecnods */</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>timeval结构的两个成员一个表示秒，一个表示微秒。timeval同样也是表示时间戳。<span style="color: red">调用utimes时，st_ctime字段会被自动更新。</span></p><h3 id="4-21-函数mkdir、mkdirat和rmdir"><a href="#4-21-函数mkdir、mkdirat和rmdir" class="headerlink" title="4.21 函数mkdir、mkdirat和rmdir"></a>4.21 函数mkdir、mkdirat和rmdir</h3><p>mkdir、mkdirat用于创建目录，rmdir用于删除目录。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/stat.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">mkdir</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *pathname, <span class="keyword">mode_t</span> mode)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">mkdirat</span><span class="params">(<span class="keyword">int</span> fd, <span class="keyword">const</span> <span class="keyword">char</span> *pathname, <span class="keyword">mode_t</span> mode)</span></span>;</span><br></pre></td></tr></table></figure><p>若成功返回0，出错返回-1。这两个函数将创建一个新的空目录，<code>.</code>和<code>..</code>是自动创建的。所指定的文件访问权限mode由进程的文件模式创建屏蔽字修改。</p><p>常见的错误是指定与文件相同的mode（只指定读、写权限）。但是，对于目录通常至少要设置一个执行权限位，以允许访问该目录中的文件名。</p><p>rmdir函数可以删除一个空目录。空目录是只能包含<code>.</code>和<code>..</code>这两项的目录。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">rmdir</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *pathname)</span></span>;</span><br></pre></td></tr></table></figure><h3 id="4-22-读目录"><a href="#4-22-读目录" class="headerlink" title="4.22 读目录"></a>4.22 读目录</h3><p>对某个目录具有访问权限的任一用户都可以读该目录，但是，为了防止文件系统产生混乱，只有内核才能写该目录。一个目录的写权限位和执行权限位决定了在该目录中能否<strong>创建新文件</strong>以及<strong>删除文件</strong>，它们并不表示能否写目录本身。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;dirent.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回指针；若出错，返回NULL */</span></span><br><span class="line"><span class="function">DIR *<span class="title">opendir</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *pathname)</span></span>;</span><br><span class="line"><span class="function">DIR *<span class="title">fdopendir</span><span class="params">(<span class="keyword">int</span> fd)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回指针；若在目录尾或出错，返回NULL */</span></span><br><span class="line"><span class="function">struct dirent *<span class="title">readdir</span><span class="params">(DIR *dp)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0；若出错，返回-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">rewinddir</span><span class="params">(DIR *dp)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">closedir</span><span class="params">(DIR *dp)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 返回与dp关联的目录中的当前位置 */</span></span><br><span class="line"><span class="function"><span class="keyword">long</span> <span class="title">telldir</span><span class="params">(DIR *dp)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">seekdir</span><span class="params">(DIR *dp, <span class="keyword">long</span> loc)</span></span>;</span><br></pre></td></tr></table></figure><p>dirent结构体至少包含以下两个成员：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ino_t</span> d_ino;       <span class="comment">/* i-node number */</span></span><br><span class="line"><span class="keyword">char</span>  d_name[];    <span class="comment">/* null-terminated filename */</span></span><br></pre></td></tr></table></figure><p>DIR结构是一个内部结构，上述7个函数用这个内部结构保存当前正在被读的目录的有关信息。</p><p>DIR结构由fdopendir创建时，readdir返回的第一项取决于传给fdopendir函数的文件描述符相关联的文件偏移量。</p><h3 id="4-23-函数chdir、fchdir和getcwd"><a href="#4-23-函数chdir、fchdir和getcwd" class="headerlink" title="4.23 函数chdir、fchdir和getcwd"></a>4.23 函数chdir、fchdir和getcwd</h3><p>进程调用chdir或fchdir函数可以更改当前工作目录。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">chdir</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *pathname)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fchdir</span><span class="params">(<span class="keyword">int</span> fd)</span></span>;</span><br></pre></td></tr></table></figure><p>若成功，返回0；若出错，返回-1。</p><p>getcwd从当前目录开始，用<code>..</code>找到其上一级目录，然后读其目录项，直到该目录项中的i节点编号与工作目录i节点编号相同，这样就找到了其对应的文件名。按照这种方法逐层上移，直到遇到根，这样就得到了当前工作目录完整的绝对路径名。</p><figure class="highlight c"><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">char</span> *<span class="title">getcwd</span><span class="params">(<span class="keyword">char</span> *buf, <span class="keyword">size_t</span> size)</span></span>;</span><br></pre></td></tr></table></figure><p>若成功，返回buf；若出错，返回NULL。必须向该函数传递两个参数，一个是缓冲区地址buf，另一个是缓冲区长度size（以字节为单位）。该缓冲区必须有足够的长度以容纳绝对路径名再加上一个终止null字节，否则返回出错。</p><p>chdir跟随符号链接，但是getcwd沿目录树向上溯源时，并不了解某个目录由符号链接所指向。</p><h3 id="4-24-设备特殊文件"><a href="#4-24-设备特殊文件" class="headerlink" title="4.24 设备特殊文件"></a>4.24 设备特殊文件</h3><p>每个<strong>文件系统</strong>所在的存储设备都由其主、次设备号表示。设备号所用的数据类型是基本系统数据类型dev_t。主设备号标识设备驱动程序，有时编码为与其通信的外设备板；次设备号标识特定的子设备。一个磁盘驱动器经常包含若干个文件系统。在同一磁盘驱动器上的各文件系统通常具有相同的主设备号，但是次设备号却不同。</p><p>系统中与每个文件名关联的st_dev值是文件系统的设备号，该文件系统包含了这一文件名以及与其对应的i节点。</p><p>只有字符特殊文件和块特殊文件才有st_rdev值。此值包含实际的设备号。</p><p>major与minor用于查看主、次设备号，是两个宏。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;dev = %d/%d\n&quot;</span>, major(buf.st_dev), minor(buf.st_dev));</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;rdev = %d/%d\n&quot;</span>, major(buf.st_rdev), minor(buf.st_rdev));</span><br></pre></td></tr></table></figure><h2 id="第5章-标准I-O库"><a href="#第5章-标准I-O库" class="headerlink" title="第5章 标准I/O库"></a>第5章 标准I/O库</h2><h3 id="5-1-引言"><a href="#5-1-引言" class="headerlink" title="5.1 引言"></a>5.1 引言</h3><p>标准I/O库处理很多细节，如缓冲区分配、以优化的块长度执行I/O等。这些处理使用户不必担心如何选择使用正确的块长度。</p><h3 id="5-2-流和FILE对象"><a href="#5-2-流和FILE对象" class="headerlink" title="5.2 流和FILE对象"></a>5.2 流和FILE对象</h3><p>第三章中的I/O操作通过打开文件得到文件描述符，进而对文件描述符进行后续I/O操作。标准I/O则是将文件与<strong>流</strong>相关联。</p><p>流的<strong>定向</strong>决定了所读写的字符是单字节还是多字节的。当一个流被创建时，它并没有定向。如果在一个未定向的流上使用多字节I/O函数，则将该流的定向设置为宽定向的。如果在一个未定向的流上使用单字节I/O函数，则将流的定向设置为单字节定向的。</p><p>有两个函数可以改变流的定向：freopen清除一个流的定向，fwide用于设置流的定向。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;wchar.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fwide</span><span class="params">(FILE *fp, <span class="keyword">int</span> mode)</span></span>;</span><br></pre></td></tr></table></figure><p>若流是宽定向的，返回正值；若流是字节定向的，返回负值；若流是未定向的，返回0。</p><p>mode参数：</p><ul><li>mode为正，fwide将试图使指定的流为宽定向的；</li><li>mode为负，fwide将试图使指定的流为字节定向的；</li><li>mode为0，不设置流的定向，返回流的定向。</li></ul><p>注意，fwide并不改变已经定向的流的定向。</p><h3 id="5-3-标准输入、标准输出和标准错误"><a href="#5-3-标准输入、标准输出和标准错误" class="headerlink" title="5.3 标准输入、标准输出和标准错误"></a>5.3 标准输入、标准输出和标准错误</h3><p>头文件stdio.h中定义：<code>stdin</code>、<code>stdout</code>、<code>stderr</code>。三个均为<code>FILE *</code>类型，即文件指针。</p><h3 id="5-4-缓冲"><a href="#5-4-缓冲" class="headerlink" title="5.4 缓冲"></a>5.4 缓冲</h3><p>标准I/O库提供缓冲的目的是尽可能减少使用read和write调用的次数，它也对每个I/O流自动地进行缓冲管理，从而避免了应用程序需要考虑这一点所带来的麻烦。</p><p>标准I/O提供了3种类型的缓冲：</p><ul><li>全缓冲：在填满标准I/O缓冲区后才进行实际I/O操作。在缓冲区未满时，可使用fflush函数冲洗一个流，将缓冲区中的内容写到磁盘上。</li><li>行缓冲：当在输入和输出中遇到换行符或行缓冲区满时，标准I/O库执行I/O操作。任何时候只要通过标准I/O库要求从一个不带缓冲的流，或者一个行缓冲的流得到输入数据，那么就会冲洗所有行缓冲输出流。</li><li>不带缓冲：不对字符进行缓冲存储。</li></ul><p>标准错误流stderr通常是不带缓冲的，这就使得出错信息可以尽快显示出来。</p><p>ISO C要求下列缓冲特征：</p><ul><li>当且仅当标准输入和标准输出并不指向交互设备时，它们才是全缓冲的</li><li>标准错误决不会是全缓冲的</li></ul><p>很多系统默认使用下列类型的缓冲：</p><ul><li>标准错误是不带缓冲的</li><li>若是指向终端设备的流，则是行缓冲的；否则是全缓冲的</li></ul><p>对于一个给定的流，如果我们并不喜欢这些系统默认，则可调用下列两个函数中的一个更改缓冲类型：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">setbuf</span><span class="params">(FILE *fp, <span class="keyword">char</span> *buf)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">setvbuf</span><span class="params">(FILE *fp, <span class="keyword">char</span> *buf, <span class="keyword">int</span> mode, <span class="keyword">size_t</span> size)</span></span>;</span><br></pre></td></tr></table></figure><p>若成功，返回0；若出错，返回非0。这些函数必须要在对文件指针执行任何一个其他操作之前调用。</p><p>setbuf用于打开或关闭缓冲，若buf参数为NULL，则为不带缓冲。否则，参数buf必须指向一个长度为BUFSIZ的缓冲区，执行后流就是全缓冲的，但如果一个流与一个终端设备相关，那么也可能将其设置为行缓冲。</p><p>setvbuf可以精确设置所需的缓冲类型，mode参数：</p><ul><li>_IOFBF：全缓冲</li><li>_IOLBF：行缓冲</li><li>_IONBF：不带缓冲</li></ul><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210516132140722.png" alt="image-20210516132140722"></p><p>任何时候，我们都可强制冲洗一个流：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fflush</span><span class="params">(FILE *fp)</span></span>;</span><br></pre></td></tr></table></figure><p>若成功，返回0；若出错，返回EOF。</p><h3 id="5-5-打开流"><a href="#5-5-打开流" class="headerlink" title="5.5 打开流"></a>5.5 打开流</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function">FILE *<span class="title">fopen</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *pathname, <span class="keyword">const</span> <span class="keyword">char</span> *type)</span></span>;</span><br><span class="line"><span class="function">FILE *<span class="title">freopen</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *pathname, <span class="keyword">const</span> <span class="keyword">char</span> *type, FILE *fp)</span></span>;</span><br><span class="line"><span class="function">FILE *<span class="title">fdopen</span><span class="params">(<span class="keyword">int</span> fd, <span class="keyword">const</span> <span class="keyword">char</span> *type)</span></span>;</span><br></pre></td></tr></table></figure><p>三个函数若成功，返回文件指针；若出错，返回NULL。</p><ul><li>fopen打开路径名pathname为一个指定的文件流</li><li>freopen在一个指定的流上打开一个指定的文件，若该流已经打开，则先关闭该流。若流已经定向，则使用freopen清除该定向。</li><li>fdopen取一个已有的文件描述符，并使一个标准的I/O流与该描述符相结合。</li></ul><p>type参数：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210516132953737.png" alt="image-20210516132953737"></p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210516133650170.png" alt="image-20210516133650170"></p><p>关闭流：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fclose</span><span class="params">(FILE *fp)</span></span>;</span><br></pre></td></tr></table></figure><p>返回值：若成功，返回0；若出错，返回EOF。在该文件被关闭之前，冲洗缓冲区中的输出数据。缓冲区中的任何输入数据被丢弃。如果标准I/O库已经为该流自动分配了一个缓冲区，则释放此缓冲区。</p><h3 id="5-6-读和写流（每次一个字符）"><a href="#5-6-读和写流（每次一个字符）" class="headerlink" title="5.6 读和写流（每次一个字符）"></a>5.6 读和写流（每次一个字符）</h3><p>一旦打开了流，则可在3种不同类型的<strong>非格式化I/O</strong>中进行选择，对其进行读、写操作：</p><ul><li>每次一个字符：getc(), fgetc(), getchar()</li><li>每次一行：fgets(), fputs()</li><li>直接I/O：fread(), fwrite()。每次I/O操作读或写某种数量的对象，而每个对象具有指定的长度。这两个函数常用于从二进制文件中每次读或写一个结构。</li></ul><h4 id="1-输入函数"><a href="#1-输入函数" class="headerlink" title="1. 输入函数"></a>1. 输入函数</h4><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">getc</span><span class="params">(FILE *fp)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fgetc</span><span class="params">(FILE *fp)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">getchar</span><span class="params">()</span></span>;</span><br></pre></td></tr></table></figure><p>三个函数若成功，返回下一个字符；若已达文件尾端或出错，返回EOF。</p><p>getc可被实现为宏，而fgetc不能实现为宏，是一个函数。调用fgetc所需的时间可能比getc要长，因为调用函数所需的时间通常长于调用宏。getchar相当于getc(stdin)。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210516135340453.png" alt="image-20210516135340453"></p><p>注意，不管是出错还是到达文件尾端，这3个函数都返回同样的值。为了区分这两种不同的情况，必须调用ferror或feof。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 两个函数的返回值：若条件为真，返回非0；否则，返回0 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">ferror</span><span class="params">(FILE *fp)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">feof</span><span class="params">(FILE *fp)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">clearerr</span><span class="params">(FILE *fp)</span></span>;</span><br></pre></td></tr></table></figure><p>在大多数实现中，为每个流在FILE对象中维护了两个标志：</p><ul><li>出错标志</li><li>文件结束标志</li></ul><p>调用clearerr可清楚这两个标志。</p><p>从流中读取数据后，可调用ungetc将字符再押送回流中。</p><figure class="highlight c"><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">ungetc</span><span class="params">(<span class="keyword">int</span> c, FILE *fp)</span></span>;</span><br></pre></td></tr></table></figure><p>若成功，返回c；若出错，返回EOF。压送回流中的字符以后又可从流中读出，但读出字符的顺序与压送回的顺序相反。</p><h4 id="2-输出函数"><a href="#2-输出函数" class="headerlink" title="2. 输出函数"></a>2. 输出函数</h4><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">putc</span><span class="params">(<span class="keyword">int</span> c, FILE *fp)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fputc</span><span class="params">(<span class="keyword">int</span> c, FILE *fp)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">putchar</span><span class="params">(<span class="keyword">int</span> c)</span></span>;</span><br></pre></td></tr></table></figure><p>若成功，返回c；若出错，返回EOF。</p><h3 id="5-7-每次一行I-O"><a href="#5-7-每次一行I-O" class="headerlink" title="5.7 每次一行I/O"></a>5.7 每次一行I/O</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">char</span> *<span class="title">fgets</span><span class="params">(<span class="keyword">char</span> *buf, <span class="keyword">int</span> n, FILE *fp)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">char</span> *<span class="title">gets</span><span class="params">(<span class="keyword">char</span> *buf)</span></span>;</span><br></pre></td></tr></table></figure><p>若成功，返回buf；若已到达文件尾或出错，返回NULL。这两个函数都指定了缓冲区的地址，读入的行将送入其中。gets从标准输入读，而fgets则从指定的流读。</p><p>fgets必须指定缓冲区的长度n，此函数一直读到下一个换行符为止，但是不超过n-1个字符，读入的字符被送入缓冲区。该缓冲区以null字节结尾。</p><p>gets是一个不推荐使用的函数，其问题是调用者在使用gets时不能指定缓冲区的长度。这样就可能造成缓冲区溢出。<strong>gets与fgets的另一个区别是，gets并不将换行符存入缓冲区中。</strong></p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210516145134449.png" alt="image-20210516145134449"></p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fputs</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *str, FILE *fp)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">puts</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *str)</span></span>;</span><br></pre></td></tr></table></figure><p>出错返回EOF。函数fputs将一个以null字节终止的字符串写到指定的流，尾端的终止符null不写出。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210516145208039.png" alt="image-20210516145208039"></p><p>puts函数会在额外输出一个换行符。</p><h3 id="5-8-标准I-O的效率"><a href="#5-8-标准I-O的效率" class="headerlink" title="5.8 标准I/O的效率"></a>5.8 标准I/O的效率</h3><h3 id="5-9-二进制I-O"><a href="#5-9-二进制I-O" class="headerlink" title="5.9 二进制I/O"></a>5.9 二进制I/O</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">size_t</span> <span class="title">fread</span><span class="params">(<span class="keyword">void</span> *ptr, <span class="keyword">size_t</span> size, <span class="keyword">size_t</span> nobj, FILE *fp)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">size_t</span> <span class="title">fwrite</span><span class="params">(<span class="keyword">const</span> <span class="keyword">void</span> *ptr, <span class="keyword">size_t</span> size, <span class="keyword">size_t</span> nobj, FILE *fp)</span></span>;</span><br></pre></td></tr></table></figure><p>两个函数的返回值：读或写的对象数。size为每个元素的长度，nobj为欲写的元素个数。</p><p>对于读，如果出错或到达文件尾，返回的数字可能小于nobj，应调用ferror或feof以判断究竟是哪一种情况。对于写，如果返回值小于nobj，则出错。</p><h3 id="5-10-定位流"><a href="#5-10-定位流" class="headerlink" title="5.10 定位流"></a>5.10 定位流</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回当前文件位置指示；若出错，返回-1L */</span></span><br><span class="line"><span class="function"><span class="keyword">long</span> <span class="title">ftell</span><span class="params">(FILE *fp)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0；若出错，返回-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fseek</span><span class="params">(FILE *fp, <span class="keyword">long</span> offset, <span class="keyword">int</span> whence)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">rewind</span><span class="params">(FILE *fp)</span></span>;</span><br></pre></td></tr></table></figure><p>ftell和fseek使用长整型long来存储和设置字节位置。rewind可以将一个流设置到文件的起始位置。where参数值可为<code>SEEK_CUR</code>、<code>SEEK_SET</code>、<code>SEEK_END</code>。</p><p>为了定位一个文本文件，whence一定要是<code>SEEKj_SET</code>，且offset只有两种值：0，或是对该文件的ftell所返回的值。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 返回值：若成功，返回当前文件位置；若出错，返回(off_t)-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">off_t</span> <span class="title">ftello</span><span class="params">(FILE *fp)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0；若出错，返回-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fseeko</span><span class="params">(FILE *fp, <span class="keyword">off_t</span> offset, <span class="keyword">int</span> whence)</span></span>;</span><br></pre></td></tr></table></figure><p>ftello、fseeko与ftell、fseek的区别就在于ftello和fseeko使用了off_t类型来存储位置。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fgetpos</span><span class="params">(FILE *fp, <span class="keyword">fpos_t</span> *pos)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fsetpos</span><span class="params">(FILE *fp, <span class="keyword">const</span> <span class="keyword">fpos_t</span> *pos)</span></span>;</span><br></pre></td></tr></table></figure><p>两个函数的返回值：若成功，返回0；若出错，返回非0。</p><p>fgetpos和fsetpos使用一个抽象数据类型<span style="color: red">fpos_t</span>记录文件的位置。这种数据类型可以根据需要定义一个足够大的数，用以记录文件位置。</p><h3 id="5-11-格式化I-O"><a href="#5-11-格式化I-O" class="headerlink" title="5.11 格式化I/O"></a>5.11 格式化I/O</h3><h4 id="1-格式化输出"><a href="#1-格式化输出" class="headerlink" title="1. 格式化输出"></a>1. 格式化输出</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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 返回值：若成功，返回输出字符数；若出错，返回负值 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">printf</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *format, ...)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fprintf</span><span class="params">(FILE *fp, <span class="keyword">const</span> <span class="keyword">char</span> *format, ...)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">dprintf</span><span class="params">(<span class="keyword">int</span> fd, <span class="keyword">const</span> <span class="keyword">char</span> *format, ...)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 返回值：若成功，返回存入数组的字符数；若编码错误，返回负值 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">sprintf</span><span class="params">(<span class="keyword">char</span> *buf, <span class="keyword">const</span> <span class="keyword">char</span> *format, ...)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若缓冲区足够大，返回将要存入数组的字符数；若编码错误，返回负值 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">snprintf</span><span class="params">(<span class="keyword">char</span> *buf, <span class="keyword">size_t</span> n, <span class="keyword">const</span> <span class="keyword">char</span> *format, ...)</span></span>;</span><br></pre></td></tr></table></figure><ul><li>printf</li><li>fprintf 写至指定的流</li><li>dprintf 写至指定的文件描述符</li><li>sprintf 将格式化的字符送入数组buf中，sprintf 在该数组的尾端自动加一个null字节，但该字符不包括在返回值中</li><li>snprintf 解决了sprintf的缓冲区溢出问题，超过缓冲区尾端写的所有字符都被丢弃。</li></ul><p>转换说明：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">%[flags][fldwidth][precision][lenmodifier]convtype</span><br></pre></td></tr></table></figure><p>flags:</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210516154855978.png" alt="image-20210516154855978"></p><p>lenmodifier:</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210516155053219.png" alt="image-20210516155053219"></p><p>convtype:</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210516155122124.png" alt="image-20210516155122124"></p><h4 id="2-格式化输入"><a href="#2-格式化输入" class="headerlink" title="2. 格式化输入"></a>2. 格式化输入</h4><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">scanf</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *format, ...)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fscanf</span><span class="params">(FILE *fp, <span class="keyword">const</span> <span class="keyword">char</span> *format, ...)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">sscanf</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *buf, <span class="keyword">const</span> <span class="keyword">char</span> *format, ...)</span></span>;</span><br></pre></td></tr></table></figure><p>scanf族用于分析输入字符串，并将字符序列转换成指定类型的变量。格式之后的个参数包含了变量的地址，用转换结果对这些变量赋值。</p><h3 id="5-12-实现细节"><a href="#5-12-实现细节" class="headerlink" title="5.12 实现细节"></a>5.12 实现细节</h3><p>标准I/O库最终都要调用第3章说明中的I/O例程。每个标准I/O流都有一个与其相关联的文件描述符，可以对一个流调用fileno函数以获得其描述符。</p><figure class="highlight c"><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fileno</span><span class="params">(FILE *fp)</span></span>;</span><br></pre></td></tr></table></figure><h3 id="5-13-临时文件"><a href="#5-13-临时文件" class="headerlink" title="5.13 临时文件"></a>5.13 临时文件</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 返回唯一路径名的指针 */</span></span><br><span class="line"><span class="function"><span class="keyword">char</span> *<span class="title">tmpnam</span><span class="params">(<span class="keyword">char</span> *ptr)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回文件指针；若出错，返回NULL */</span></span><br><span class="line"><span class="function">FILE *<span class="title">tmpfile</span><span class="params">(<span class="keyword">void</span>)</span></span>;</span><br></pre></td></tr></table></figure><p>tmpnam函数产生一个与现有文件名不同的一个有效路径名字符串。每次调用它时，都产生一个不同的路径名，最多调用次数是TMP_MAX。TMP_MAX定义在stdio.h中。如果ptr参数为NULL，则所产生的路径名存放在一个静态区中，指向静态区的指针作为函数值返回。后续调用tmpnam时，会重写该静态区。如果prt不是NULL，则认为它应该是指向长度至少是L_tmpnam个字符的数组（L_tmpnam是一个常量，定义在stdio.h中）。所产生的路径名存放在该数组中，prt也作为函数值返回。</p><p>tmpfile创建一个临时二进制文件（类型wb+），在关闭该文件或程序结束时将自动删除这种文件。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回指向目录名的指针；若出错，返回NULL */</span></span><br><span class="line"><span class="function"><span class="keyword">char</span> *<span class="title">mkdtemp</span><span class="params">(<span class="keyword">char</span> *<span class="keyword">template</span>)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回文件描述符；若出错，返回-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">mkstemp</span><span class="params">(<span class="keyword">char</span> *<span class="keyword">template</span>)</span></span>;</span><br></pre></td></tr></table></figure><p>mkdtemp函数创建了一个目录，该目录有一个唯一的名字；mkstemp函数创建了一个文件，该文件有一个唯一的名字。名字是通过template字符串进行选择的。这个字符串是后6位设置为XXXXXX的路径名。函数将这些占位符替换成不同的字符串来构建一个唯一的路径名。如果成功的话，这两个函数将修改template字符串反映临时文件的名字。</p><p>mkdtemp函数创建的目录使用下列访问权限位：S_IRUSR、S_IWUSR、S_IXUSR。mkstemp函数以唯一的名字创建一个普通文件并且打开该文件，该函数返回的文件描述符以读写方式打开，访问权限位设置为：S_IRUSR、S_IWUSR。</p><p>与tmpfile不同的是，mkstemp创建的临时文件并不会自动删除。</p><h2 id="第6章-系统数据文件和信息"><a href="#第6章-系统数据文件和信息" class="headerlink" title="第6章 系统数据文件和信息"></a>第6章 系统数据文件和信息</h2><h3 id="6-1-引言"><a href="#6-1-引言" class="headerlink" title="6.1 引言"></a>6.1 引言</h3><p><strong>系统文件</strong>：</p><ul><li>口令文件：/etc/passwd</li><li>组文件：/etc/group</li></ul><p>UNIX系统的正常运作需要使用大量与系统有关的数据文件，这些数据文件都是ASCII文本文件，并且使用标准I/O库读这些文件。但是，对于较大的系统，顺序扫描口令文件很花费时间，我们需要能够以非ASCII文本格式存放这些文件，但仍向使用其他文件格式的应用程序提供接口。</p><p><strong>为应用程序提供对数据文件的可移植接口</strong>是本章的主题。本章也包括了系统标识函数、时间和日期函数。</p><h3 id="6-2-口令文件"><a href="#6-2-口令文件" class="headerlink" title="6.2 口令文件"></a>6.2 口令文件</h3><p>头文件pwd.h中定义的passwd结构：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210517091026848.png" alt="image-20210517091026848"></p><p>/etc/passwd（Linux）:</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210517091205402.png" alt="image-20210517091205402"></p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210517091706896.png" alt="image-20210517091706896"></p><ul><li>空白注释字段不产生任何影响</li><li>/usr/sbin/nologin 打印可定制的出错信息，然后以非0状态终止，可阻止一个特定用户登陆系统</li><li>nobody用户名使任何人都可登陆至系统，但其用户ID（65534）和组ID（65534）不提供任何特权。该用户ID和组ID只能访问人人皆可读、写的文件</li></ul><p>两个获取口令文件项的函数：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pwd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function">struct passwd *<span class="title">getpwuid</span><span class="params">(<span class="keyword">uid_t</span> uid)</span></span>;</span><br><span class="line"><span class="function">struct passwd *<span class="title">getpwnam</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *name)</span></span>;</span><br></pre></td></tr></table></figure><p>若成功，返回指向passwd结构的指针；若出错，返回NULL。</p><p>passwd结构通常是函数内部的静态变量，只要调用任一相关函数，其内容就会被重写。</p><p>有些程序要查看整个口令文件：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pwd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回指针；若出错或到达文件尾，返回NULL */</span></span><br><span class="line"><span class="function">struct passwd *<span class="title">getpwent</span><span class="params">(<span class="keyword">void</span>)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">setpwent</span><span class="params">(<span class="keyword">void</span>)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">endpwent</span><span class="params">(<span class="keyword">void</span>)</span></span>;</span><br></pre></td></tr></table></figure><p>调用getpwend时，它返回口令文件中的下一个记录项。每次调用此函数都重写passwd结构。第一次调用时，它打开所使用的各个文件。在使用getpwent查看完口令文件后，一定要调用endpwent关闭这些文件。getpwent对返回记录项的顺序并无要求。</p><p>setpwent反绕它所使用的文件。</p><h3 id="6-3-阴影口令"><a href="#6-3-阴影口令" class="headerlink" title="6.3 阴影口令"></a>6.3 阴影口令</h3><p>加密口令是经<strong>单向</strong>加密算法处理过的用户口令副本。对于一个加密口令，找不到一种算法可以将其反变换到明文口令。</p><p>阴影口令（shadow password）文件中存放这些加密过的口令，与之相关的结构：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210517094911742.png" alt="image-20210517094911742"></p><p>只有用户登陆名和加密口令这两个字段是必须的。阴影口令文件（/etc/shadow）文件只有root用户可读，而/etc/passwd文件可由各用户自由读取。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;shadow.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回指针；若出错，返回NULL */</span></span><br><span class="line"><span class="function">struct spwd *<span class="title">getspnam</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *name)</span></span>;</span><br><span class="line"><span class="function">struct spwd *<span class="title">getspent</span><span class="params">(<span class="keyword">void</span>)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">setspent</span><span class="params">(<span class="keyword">void</span>)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">endspent</span><span class="params">(<span class="keyword">void</span>)</span></span>;</span><br></pre></td></tr></table></figure><h3 id="6-4-组文件"><a href="#6-4-组文件" class="headerlink" title="6.4 组文件"></a>6.4 组文件</h3><p>/etc/group为组文件。与之对应的C结构体是在头文件grp.h中的group：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210517095502842.png" alt="image-20210517095502842"></p><p>gr_men是一个指针数组，其中每一个指针指向一个属于该组的用户名。该数组以null指针结尾。</p><p>可以用下列两个函数来获取一个group的信息：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;grp.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回指针；若出错，返回NULL */</span></span><br><span class="line"><span class="function">struct group *<span class="title">getgrgid</span><span class="params">(<span class="keyword">gid_t</span> gid)</span></span>;</span><br><span class="line"><span class="function">struct group *<span class="title">getgrnam</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *name)</span></span>;</span><br></pre></td></tr></table></figure><p>如同对口令文件进行操作的函数一样，这两个函数通常也返回指向一个静态变量的指针，在每次调用时都重写该静态变量。</p><p>搜索整个组文件，使用另外几个函数：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;grp.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回指针；若出错或到达文件尾端，返回NULL */</span></span><br><span class="line"><span class="function">struct group *<span class="title">getgrent</span><span class="params">(<span class="keyword">void</span>)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">setgrent</span><span class="params">(<span class="keyword">void</span>)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">endgrent</span><span class="params">(<span class="keyword">void</span>)</span></span>;</span><br></pre></td></tr></table></figure><h3 id="6-5-附属组ID（supplementary-group-ID）"><a href="#6-5-附属组ID（supplementary-group-ID）" class="headerlink" title="6.5 附属组ID（supplementary group ID）"></a>6.5 附属组ID（supplementary group ID）</h3><p>我们不仅可以属于口令文件记录项中组ID所对应的组，也可属于多至16各另外的组。文件访问权限检查修改为：不仅将进程的有效组ID与文件的组ID相比较，而且也将所有附属组ID与文件的组ID进行比较。</p><p>使用附属组ID的优点是不必再显式地经常更改组。一个用户会参与多个项目，因此也就要同时属于多个组，此类情况是常有的。</p><h3 id="6-6-实现区别"><a href="#6-6-实现区别" class="headerlink" title="6.6 实现区别"></a>6.6 实现区别</h3><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210517100902560.png" alt="image-20210517100902560"></p><h3 id="6-7-其他数据文件"><a href="#6-7-其他数据文件" class="headerlink" title="6.7 其他数据文件"></a>6.7 其他数据文件</h3><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210517101250237.png" alt="image-20210517101250237"></p><p>一般情况下，对于每个数据文件，至少有3各函数：</p><ul><li>get函数：读下一个记录，如果需要，还会打开该文件。此种函数通常返回指向一个结构的指针。当已达到文件尾端时返回空指针。大多数get函数返回指向一个静态存储类结构的指针，如果要保存它，则需复制它。</li><li>set函数：打开相应的数据文件（如果尚未打开），然后反绕该文件。如果希望在相应文件起始处开始处理，则调用此函数。</li><li>end函数：关闭相应数据文件。</li></ul><p>另外，还可支持某种形式的键搜索，如getpwnam、getpwuid等等。</p><h3 id="6-8-登陆账户记录"><a href="#6-8-登陆账户记录" class="headerlink" title="6.8 登陆账户记录"></a>6.8 登陆账户记录</h3><p>大多数UNIX系统都提供下列两个数据文件：</p><ul><li>utmp：记录当前登陆到系统的各个用户，对应命令who（/var/run/utmp）</li><li>wtmp：跟踪各个登陆和注销事件，对应命令last（/var/log/wtmp）</li></ul><p>对应的结构体：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">utmp</span> &#123;</span></span><br><span class="line">    <span class="keyword">char</span> ut_line[<span class="number">8</span>];   <span class="comment">/* tty line: &quot;ttyh0&quot;, &quot;ttyd0&quot;, &quot;ttyp0&quot;, ... */</span></span><br><span class="line">    <span class="keyword">char</span> ut_name[<span class="number">8</span>];   <span class="comment">/* login time */</span></span><br><span class="line">    <span class="keyword">long</span> ut_time;      <span class="comment">/* seconds since Epoch */</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>登陆时，login程序填写此类型结构，然后将其写入到utmp文件中，同时也将其填写到wtmp文件中。注销时，init进程将utmp文件中相应的记录擦除（每个字节都填以null字节），并将一个新记录填写到wtmp文件中。</p><h3 id="6-9-系统标识"><a href="#6-9-系统标识" class="headerlink" title="6.9 系统标识"></a>6.9 系统标识</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/utsname.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">uname</span><span class="params">(struct utsname *name)</span></span>;</span><br></pre></td></tr></table></figure><p>uname函数返回与主机和操作系统有关的信息，若成功，返回非负值；若出错，返回-1。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210517102628960.png" alt="image-20210517102628960"></p><p>utsname结构中，每个字段都是一个字符数组，每个数组的长度由实现确定。每个字符串都以null字节结尾。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210517102827921.png" alt="image-20210517102827921"></p><p>BSD派生的系统提供gethostname函数，它只返回主机名，该名字通常就是TCP/IP网络上主机的名字。</p><figure class="highlight c"><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">gethostname</span><span class="params">(<span class="keyword">char</span> *name, <span class="keyword">int</span> namelen)</span></span>;</span><br></pre></td></tr></table></figure><p>若成功，返回0；若出错，返回-1。namelen参数指定name缓冲区长度，如若提供足够的空间，则通过name返回的字符串以null字节结尾。如果没有提供足够的空间，则没有说明通过name返回的字符串是否以null结尾。相关联的文件是/etc/hostname。</p><h3 id="6-10-时间和日期例程"><a href="#6-10-时间和日期例程" class="headerlink" title="6.10 时间和日期例程"></a>6.10 时间和日期例程</h3><p>time函数用于获取日历时间（从UTC到现在的秒数）：</p><figure class="highlight c"><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;time.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">time_t</span> <span class="title">time</span><span class="params">(<span class="keyword">time_t</span> *calptr)</span></span>;</span><br></pre></td></tr></table></figure><p>若成功，返回时间值；若出错，返回-1。如果参数非空，则时间值也存放在由calptr指向的单元内。</p><p>clock_gettime函数可用于获取指定时钟的时间，将时间存在timespce结构中，它把时间表示为秒和纳秒：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/time.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0；若出错，返回-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">clock_gettime</span><span class="params">(<span class="keyword">clockid_t</span> clock_id, struct timespec *tsp)</span></span>;</span><br></pre></td></tr></table></figure><p>clock_id的类型为clockid_t：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210517104625328.png" alt="image-20210517104625328"></p><p>clock_id设置为CLOCK_REALTIME时，clock_gettime函数提供了与time函数类似的功能，不过在系统支持高精度时间值的情况下，clock_gettime可能比time函数得到更高精度的时间值。</p><p>clock_getres函数把参数tsp指向的timespec结构初始化为与clock_id参数对应的时钟精度。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/time.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0；若出错，返回-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">clock_getres</span><span class="params">(<span class="keyword">clockid_t</span> clock_id, struct timespec *tsp)</span></span>;</span><br></pre></td></tr></table></figure><p>要对特定的时钟设置时间，可以调用clock_settime函数：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/time.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0；若出错，返回-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">clock_settime</span><span class="params">(<span class="keyword">clockid_t</span> clock_id, <span class="keyword">const</span> struct timespec *tsp)</span></span>;</span><br></pre></td></tr></table></figure><p>gettimeofday函数以距特定时间（1970年1月1日00:00:00）的秒数的方式将当前时间存放在tp指向的timeval结构中，而该结构将当前时间表示为为秒和微妙。</p><figure class="highlight c"><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/time.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">gettimeofday</span><span class="params">(struct timeval *tp, <span class="keyword">void</span> *tzp)</span></span>;</span><br></pre></td></tr></table></figure><p>tzp的唯一合法值是NULL，其他值将产生不确定的结构。某些平台支持用tzp说明时区，但这完全依赖于实现。</p><p>一旦获取这种从上述特定时间经过的秒数的整型时间值后，通常要调用函数将其转换为分解的时间结构，然后调用另一个函数生成人们可读的时间和日期。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210517110330679.png" alt="image-20210517110330679"></p><p>上图中表示了时间转换关系，虚线表示受到环境变量TZ的影响。点划线标示了如何从时间相关的结构获得日历时间。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;time.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 出错返回NULL */</span></span><br><span class="line"><span class="function">struct tm *<span class="title">gmtime</span><span class="params">(<span class="keyword">const</span> <span class="keyword">time_t</span> *calptr)</span></span>;</span><br><span class="line"><span class="function">struct tm *<span class="title">localtime</span><span class="params">(<span class="keyword">const</span> <span class="keyword">time_t</span> *calptr)</span></span>;</span><br></pre></td></tr></table></figure><p>localtime和gmtime将日历时间转换成分解的时间，并将这些存放在一个tm结构中：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210517110736428.png" alt="image-20210517110736428" style="zoom:67%;" /></p><p>除了月日字段，其他字段的值都以0开始。如果夏令时生效，则夏令时标志值为正；如果为非夏令时时间，则该标志值为0，如果此信息不可用，则其值为负。</p><p>localtime和gmtime的区别是：localtime将日历时间转换成本地时间（考虑本地时区和夏令时标志），而gmtime则将日历时间转换成协调统一时间的年、月、日、时、分、秒、周日分解结构。</p><p>函数mktime以本地时间的年、月、日等作为参数，将其变换成time_t值。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;time.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回日历时间；若出错，返回-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">time_t</span> <span class="title">mktime</span><span class="params">(struct tm *tmptr)</span></span>;</span><br></pre></td></tr></table></figure><p>函数strftime是一个类似于printf的时间值函数，非常复杂，可以通过可用的多个参数来定制产生的字符串。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;time.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">size_t</span> <span class="title">strftime</span><span class="params">(<span class="keyword">char</span> *buf, <span class="keyword">size_t</span> maxsize, <span class="keyword">const</span> <span class="keyword">char</span> *format, <span class="keyword">const</span> struct tm *tmptr)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">size_t</span> <span class="title">strftime_l</span><span class="params">(<span class="keyword">char</span> *buf, <span class="keyword">size_t</span> maxsize, <span class="keyword">const</span> <span class="keyword">char</span> *format, <span class="keyword">const</span> struct tm *tmptr, <span class="keyword">locale_t</span> locale)</span></span>;</span><br></pre></td></tr></table></figure><p>若有空间，返回存入数组的字符数（在末尾加上null终止符，但该字符不算在返回字符数中）；否则，返回0。strftime_l允许调用者将区域指定为参数，除此之外，strftime和strftime_l函数是相同的。strftime使用通过TZ环境变量指定的区域。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210517112208563.png" alt="image-20210517112208563"></p><p>strptime函数是strftime的反过来版本，把字符串时间转换成分解时间。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;time.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回上一次解析的字符的下一字符的指针；出错返回NULL */</span></span><br><span class="line"><span class="function"><span class="keyword">char</span> *<span class="title">strptime</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *buf, <span class="keyword">const</span> <span class="keyword">char</span> *format, struct tm *tmptr)</span></span>;</span><br></pre></td></tr></table></figure><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210517113214260.png" alt="image-20210517113214260"></p><h2 id="第7章-进程环境"><a href="#第7章-进程环境" class="headerlink" title="第7章 进程环境"></a>第7章 进程环境</h2><h3 id="7-1-引言"><a href="#7-1-引言" class="headerlink" title="7.1 引言"></a>7.1 引言</h3><p>本章将学习：</p><ul><li>当程序执行时，其main函数是如何被调用的</li><li>命令行参数是如何传递给新程序的</li><li>典型的存储空间布局是什么样式的</li><li>如何分配另外的存储空间</li><li>进程如何使用环境变量</li><li>进程的各种不同终止方式</li><li>longjum和setjmp函数以及它们与栈的交互作用</li><li>进程的资源限制</li></ul><h3 id="7-2-main函数"><a href="#7-2-main函数" class="headerlink" title="7.2 main函数"></a>7.2 main函数</h3><p>C程序的main函数原型：</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="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">char</span> *argv[])</span></span>;</span><br></pre></td></tr></table></figure><p>当内核执行C程序时（使用一个exec函数），在调用main前先调用一个特殊的启动例程。可执行程序文件将此启动例程指定为程序的起始地址——这是由连接编辑器设置的，而连接编辑器则由C编译器调用。启动例程从内核取得命令行参数和环境变量值，然后为按上述方式调用main函数做好安排。</p><h3 id="7-3-进程终止"><a href="#7-3-进程终止" class="headerlink" title="7.3 进程终止"></a>7.3 进程终止</h3><p>8种终止进程的方式，前5种为正常终止：</p><ul><li>从 main 返回</li><li>调用 exit</li><li>调用 _exit 或 _Exit</li><li>最后一个线程从其启动例程返回</li><li>从最后一个线程调用 pthread_exit</li></ul><p>3种异常终止方式：</p><ul><li>调用abort</li><li>接到一个信号</li><li>最后一个线程对取消请求做出响应</li></ul><p>上一节提及的启动例程是这样编写的，使得从main返回后立即调用exit函数。如果将启动例程以C代码形式表示（实际上该例程常常使用汇编语言编写），则它调用main函数的形式可能是：</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">exit</span>(main(argc, argv));</span><br></pre></td></tr></table></figure><h4 id="1-退出函数"><a href="#1-退出函数" class="headerlink" title="1. 退出函数"></a>1. 退出函数</h4><p>3个函数用于正常终止一个程序：_exit和_Exit立即进入内核，exit则先执行一些清理处理，然后返回内核。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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="meta-keyword">include</span> <span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">exit</span><span class="params">(<span class="keyword">int</span> status)</span></span>;</span><br><span class="line"><span class="keyword">void</span> _Exit(<span class="keyword">int</span> status);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> _exit(<span class="keyword">int</span> status);</span><br></pre></td></tr></table></figure><p>exit函数总是执行一个标准I/O库的清理关闭操作：对于所有打开的流，调用fclose函数，这造成输出缓冲中的所有数据都被冲洗（写到文件上）。</p><p>3个退出函数都有一个整型参数，称为<strong>终止状态</strong>。大多数UNIX系统shell都提供检查进程终止状态的方法。如果：</p><ul><li>调用这些函数时不带终止状态</li><li>main执行了一个无返回值的return</li><li>main没有声明返回类型为整型</li></ul><p>则该进程的终止状态是未定义的。但是，若main的返回类型是整型，并且main执行到最后一条语句（隐式返回），那么该进程的终止状态是0。</p><h4 id="2-函数atexit"><a href="#2-函数atexit" class="headerlink" title="2. 函数atexit"></a>2. 函数atexit</h4><p>atexit函数用于注册<strong>终止处理程序（exit handler）</strong>，这些函数注册后由exit自动调用。</p><figure class="highlight c"><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">atexit</span><span class="params">(<span class="keyword">void</span> (*func)(<span class="keyword">void</span>))</span></span>;  <span class="comment">/* 若成功，返回0；若出错，返回非0 */</span></span><br></pre></td></tr></table></figure><p>exit调用这些函数的顺序与它们注册时候的顺序相反，同一函数若登记多次，也会被调用多次。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210518090917814.png" alt="image-20210518090917814"></p><p>exit首先调用各终止处理程序，然后关闭所有打开流。内核使程序执行的唯一方法是调用一个exec函数。进程自愿终止的唯一方法是显式或隐式地调用_exit或_Exit。进程也可非自愿地由一个信号使其终止。</p><h3 id="7-4-命令行参数"><a href="#7-4-命令行参数" class="headerlink" title="7.4 命令行参数"></a>7.4 命令行参数</h3><p>当执行一个程序时，调用exec的进程可将命令行参数传递给该新程序。</p><p>解析命令行参数：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">char</span> *argv[])</span> </span>&#123;</span><br><span class="line">    <span class="keyword">int</span> flags, opt;</span><br><span class="line">    <span class="keyword">int</span> nsecs, tfnd;</span><br><span class="line"></span><br><span class="line">    nsecs = <span class="number">0</span>;</span><br><span class="line">    tfnd = <span class="number">0</span>;</span><br><span class="line">    flags = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">while</span> ((opt = getopt(argc, argv, <span class="string">&quot;nt:&quot;</span>)) != <span class="number">-1</span>) &#123;</span><br><span class="line">        <span class="keyword">switch</span> (opt) &#123;</span><br><span class="line">            <span class="keyword">case</span> <span class="string">&#x27;n&#x27;</span>:</span><br><span class="line">                flags = <span class="number">1</span>;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> <span class="string">&#x27;t&#x27;</span>:</span><br><span class="line">                nsecs = atoi(optarg);</span><br><span class="line">                tfnd = <span class="number">1</span>;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">default</span>:</span><br><span class="line">                <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">&quot;Usage: %s [-t nsecs] [-n] name\n&quot;</span>, argv[<span class="number">0</span>]);</span><br><span class="line">                <span class="built_in">exit</span>(EXIT_FAILURE);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;name argument = %s\n&quot;</span>, argv[optind]);</span><br><span class="line">    <span class="built_in">exit</span>(EXIT_SUCCESS);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="7-5-环境表"><a href="#7-5-环境表" class="headerlink" title="7.5 环境表"></a>7.5 环境表</h3><p>每个程序都接收到一张<strong>环境表</strong>。与参数表一样，环境表也是一个字符指针数组，其中每个指针包含一个以空字符结尾的C风格字符串的地址。全局变量environ则包含了该指针数组的地址：</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="keyword">extern</span> <span class="keyword">char</span> **environ;</span><br></pre></td></tr></table></figure><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210518093341245.png" alt="image-20210518093341245"></p><p>我们称 environ 为<strong>环境指针</strong>，各指针指向的字符串为<strong>环境字符串</strong>。通常使用getenv和putenv函数来访问特定的环境变量，而不是用environ变量。但是，如果要查看整个环境，则必须使用environ指针。</p><h3 id="7-6-C程序的存储空间布局（内存模型）"><a href="#7-6-C程序的存储空间布局（内存模型）" class="headerlink" title="7.6 C程序的存储空间布局（内存模型）"></a>7.6 C程序的存储空间布局（内存模型）</h3><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210518093910657.png" alt="image-20210518093910657"></p><p>历史沿袭至今，C程序一直由以下几部分组成：</p><ul><li><p>正文段：这是由CPU执行的机器指令部分。通常，正文段是<strong>可共享的</strong>，所以即使是频繁执行的程序（如编译器、shell等）在存储器中也只需有一个副本。另外，正文段常常是<strong>只读的</strong>，以防止程序由于意外而修改其指令。</p></li><li><p>初始化数据段：通常将此段称为<strong>数据段</strong>，它包含了程序中<strong>需明确地赋初值的变量</strong>。例如，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="keyword">int</span> maxcount = <span class="number">99</span>;</span><br></pre></td></tr></table></figure></li><li><p>未初始化数据段：通常将此段称为<strong>bss段</strong>，在程序开始执行之前，内核将此段中的数据初始化为0或空指针。如函数外的声明：</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="keyword">long</span> sum[<span class="number">1000</span>];</span><br></pre></td></tr></table></figure></li><li><p>栈：自动变量以及每次函数调用时所需保存的信息都存放在此段中。每次函数调用时，其返回地址以及调用者的环境信息都存放在栈中。然后，最近被调用的函数在栈上为其自动和临时变量分配存储空间。通过以这种方式使用栈，C递归函数可以工作。递归函数每次调用自身时，就用一个新的栈帧，因此一次函数调用实例中的变量集不会影响另一次函数调用实例中的变量。</p></li><li><p>堆：通常在堆中进行动态内存分配。</p></li></ul><p>从图7-6中可以看出，未初始化数据段的内容并不存放在磁盘程序文件中。其原因是，内核在程序开始运行前将它们都设置为0。需要存放在磁盘程序文件中的段只有<strong>正文段</strong>和<strong>初始化数据段</strong>。</p><p>size命令报告正文段、数据段和bss段的长度（以字节为单位）：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210518095640123.png" alt="image-20210518095640123"></p><h3 id="7-7-共享库"><a href="#7-7-共享库" class="headerlink" title="7.7 共享库"></a>7.7 共享库</h3><p>共享库使得可执行文件中不再需要包含公用的库函数，而只需在所有进程都可引用的存储区中保存这种例程的一个副本。程序第一次执行或第一次调用某个函数库时，用动态链接方法将程序与共享库函数相链接。这减少了每个可执行文件的长度，但增加了一些运行时间开销。这种时间开销发生在该程序第一次被执行时，或者每个共享库函数第一次被调用时。</p><p>共享库的另一个优点是可以用库函数的新版本代替老版本而无需对使用该库的程序重新连接编辑。</p><p>下面是一个例子，不使用共享库：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210518100415727.png" alt="image-20210518100415727"></p><p>使用共享库：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210518100442134.png" alt="image-20210518100442134"></p><p>使用共享库后，执行文件的正文段和数据段的长度都显著减小。</p><h3 id="7-8-存储空间分配"><a href="#7-8-存储空间分配" class="headerlink" title="7.8 存储空间分配"></a>7.8 存储空间分配</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 三个函数返回值：若成功，返回非空指针；若出错，返回NULL */</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> *<span class="title">malloc</span><span class="params">(<span class="keyword">size_t</span> size)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> *<span class="title">calloc</span><span class="params">(<span class="keyword">size_t</span> nobj, <span class="keyword">size_t</span> size)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> *<span class="title">realloc</span><span class="params">(<span class="keyword">void</span> *ptr, <span class="keyword">size_t</span> newsize)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">free</span><span class="params">(<span class="keyword">void</span> *ptr)</span></span>;</span><br></pre></td></tr></table></figure><ul><li>malloc 分配指定字节数的存储区，此存储区的初始值不确定（垃圾数据）。</li><li>calloc 为指定数量指定长度的对象分配空间，该空间中每一位都初始化为0。</li><li>realloc 增加或减少以前分配区的长度。当增加长度时，可能需要将以前分配区的内容移到另一个足够大的区域（该过程由realloc自动完成），以便在尾端提供增加的存储区，而<strong>新增区域内的初始值则不确定</strong>。注意，realloc的第二个参数是存储区的新长度，不是新、旧长度之差。作为一个特例，若ptr是一个空指针，则realloc的功能与malloc相同，用于分配一个指定长度为newsize的存储区。</li></ul><p>致命性错误：</p><ul><li>释放一个已经释放了的块；</li><li>调用free时所使用的指针不是3个alloc函数的返回值</li></ul><h5 id="替代的存储空间分配程序-alloca"><a href="#替代的存储空间分配程序-alloca" class="headerlink" title="替代的存储空间分配程序 - alloca"></a>替代的存储空间分配程序 - alloca</h5><p>alloca是在当前函数的栈帧上分配存储空间，而不是在堆中。</p><p>优点：当函数返回时，自动释放它所使用的栈帧，所以不必再为释放空间而费心。</p><p>缺点：alloca函数增加了栈帧的长度，有些系统在函数被调用后不能增加栈帧长度，于是也就不支持alloca函数。</p><h3 id="7-9-环境变量"><a href="#7-9-环境变量" class="headerlink" title="7.9 环境变量"></a>7.9 环境变量</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">char</span> *<span class="title">getenv</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *name)</span></span>;  <span class="comment">/* 若找到，返回指向与name关联的value的指针；若未找到，返回NULL */</span></span><br></pre></td></tr></table></figure><p>我们应当使用getenv从环境变量中取一个指定环境变量的值，而不是直接访问environ。</p><p>环境变量：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210518103458708.png" alt="image-20210518103458708"></p><p>修改环境变量：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0；若出错，返回非0 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">putenv</span><span class="params">(<span class="keyword">char</span> *str)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0；若出错，返回-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">setenv</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *name, <span class="keyword">const</span> <span class="keyword">char</span> *value, <span class="keyword">int</span> rewrite)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">unsetenv</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *name)</span></span>;</span><br></pre></td></tr></table></figure><ul><li>putenv 取形式为 <em>name=value</em> 的字符串，将其放到环境表中。如果name已经存在，则先删除其原来的定义。</li><li><p>setenv 将 <em>name</em> 设置为 <em>value</em>。如果环境中 <em>name</em> 已经存在，那么使用 rewrite 来设置是否删除现有定义（rewrite = 1，删除现有定义）。</p></li><li><p>unsetenv 删除 <em>name</em> 的定义。即使不存在这种定义也不算出错。</p></li></ul><blockquote><p>注意，putenv 和 setenv 之间的区别。setenv 必须分配存储空间，以便依据其参数创建 <em>name = value</em> 字符串。putenv 可以自由地将传递给它的参数字符串直接放到环境中。确实，许多实现就是这么做的，因此，将存访在栈中的字符串作为参数传递给 putenv 就会发生错误，其原因是，从当前函数返回时，其栈帧占用的存储区可能被重用。</p></blockquote><p>环境表在进程的内存空间中处于顶部，删除一个环境字符串很简单，只要将后续指针前移。增加一个环境字符串则困难的多，既不能向上扩展，也不能向下扩展（下为栈），该空间的长度不能再增加。</p><ol><li>修改现有的name<ul><li>如果新 value 的长度少于或等于现有的 value 的长度，只要将新字符串复制到原字符串所用的空间中</li><li>如果新 value 的长度大于现有的 value 的长度，必须调用malloc为新字符串分配空间并将其复制到新分配的空间中，接着使用环境表中针对 name 的指针指向新分配区</li></ul></li><li>增加一个新的 name，首先，必须调用malloc为 <em>name=value</em> 字符串分配空间，然后将此字符串复制到新分配空间中<ul><li>如果这是第一次增加一个新 name，必须调用malloc为新的指针表分配空间，接着，将原来的环境表复制到新分配区，并将指向新 <em>name=value</em> 字符串的指针存放在该指针的表尾，然后又将一个空指针存放其后。最后使用environ指向新的指针表。</li><li>如果不是第一次增加新name，只需调用realloc，分配比原空间多存放一个指针的空间。然后将新向 <em>name=value</em> 字符串的指针放在该表表尾，后面跟着一个空指针。</li></ul></li></ol><h3 id="7-10-setjmp-和-longjmp"><a href="#7-10-setjmp-和-longjmp" class="headerlink" title="7.10 setjmp 和 longjmp"></a>7.10 setjmp 和 longjmp</h3><p>在C中，goto语句是不能跨越函数的，而执行这种类型跳转功能的是非局部goto——setjmp 和 longjmp函数。非局部指的是在栈上跳过若干调用帧，返回到当前函数调用路径上的某一个函数中。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;setjmp.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若直接调用，返回0；若从longjmp返回，则为longjmp设置的val值 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">setjmp</span><span class="params">(jmp_buf env)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">longjmp</span><span class="params">(jmp_buf env, <span class="keyword">int</span> val)</span></span>;</span><br></pre></td></tr></table></figure><h5 id="自动变量、寄存器变量和易失变量"><a href="#自动变量、寄存器变量和易失变量" class="headerlink" title="自动变量、寄存器变量和易失变量"></a>自动变量、寄存器变量和易失变量</h5><p>使用 longjmp 回到 setjmp 处时，自动变量、寄存器变量状态如何？看情况。大多数实现并不回滚这些自动变量和寄存器的值。</p><ul><li>静态变量、全局变量、volatile修饰的自动变量、存储在内存上的自动变量不会回滚</li><li>寄存器变量、自动变量（使用优化编译后存储在寄存器上）会回滚</li></ul><p>或者可以说存储在内存中的变量不会回滚，而存储在寄存器上的变量会回滚。</p><h3 id="7-11-getrlimit-和-setrlimit"><a href="#7-11-getrlimit-和-setrlimit" class="headerlink" title="7.11 getrlimit 和 setrlimit"></a>7.11 getrlimit 和 setrlimit</h3><p>每个进程都有一组资源限制，其中一些可以使用 getrlimit 和 setrlimit 函数查询和更改。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/resource.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0；若出错，返回非0 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">getrlimit</span><span class="params">(<span class="keyword">int</span> resource, struct rlimit *rlptr)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">setrlimit</span><span class="params">(<span class="keyword">int</span> resource, <span class="keyword">const</span> struct rlimit *rlptr)</span></span>;</span><br></pre></td></tr></table></figure><figure class="highlight c"><table><tr><td class="gutter"><pre><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="class"><span class="keyword">struct</span> <span class="title">rlimit</span> &#123;</span></span><br><span class="line">    <span class="keyword">rlim_t</span> rlim_cur;  <span class="comment">/* soft limit: current limit */</span></span><br><span class="line">    <span class="keyword">rlim_t</span> rlim_max;  <span class="comment">/* hard limit: maximum value for rlim_cur */</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>进程的资源限制通常是在系统初始化时由0进程建立的，然后由后续进程继承。每种实现都可以用自己的方法对资源限制做出调整。</p><ul><li><p>软限制可以被任何进程修改，修改后其值必须<span style="color: red">小于或等于其硬限制</span>。</p></li><li><p>硬限制可以被任何进程降低，但必须<span style="color: red">大于等于其软限制值</span>，且这种降低对普通用户而言是不可逆的。</p></li><li><p>只有超级用户进程可以提升硬限制值。</p></li></ul><p>常量<code>RLIM_INFINITY</code>指定了一个无限量的限制。</p><h2 id="第8章-进程控制"><a href="#第8章-进程控制" class="headerlink" title="第8章 进程控制"></a>第8章 进程控制</h2><h3 id="8-1-引言"><a href="#8-1-引言" class="headerlink" title="8.1 引言"></a>8.1 引言</h3><ul><li>进程控制：创建新进程、执行程序、进程终止</li><li>进程属性的各种ID——实际、有效和保存的用户ID和组ID</li><li>解释器文件</li><li>system函数</li><li>进程会计机制</li></ul><h3 id="8-2-进程标识"><a href="#8-2-进程标识" class="headerlink" title="8.2 进程标识"></a>8.2 进程标识</h3><p>每个进程都有一个非负整型表示的唯一进程ID。虽然是唯一的，但进程ID是可复用的，当一个进程终止后，其进程ID就成为复用的候选者。</p><ul><li>0：调度进程，常被称为交换进程，是内核的一部分，不执行任何磁盘上的程序。</li><li><p>1：init进程，在自举过程结束时由内核调用。init通常读取与系统有关的初始化文件，并将系统引导到一个状态。init进程绝不会终止。<strong>它是一个普通的用户进程，但是它以超级用户特权运行</strong>。</p></li><li><p>2：页守护进程，负责支持虚拟存储器系统的分页操作。</p></li></ul><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">pid_t</span> <span class="title">getpid</span><span class="params">(<span class="keyword">void</span>)</span></span>;  <span class="comment">// 进程ID</span></span><br><span class="line"><span class="function"><span class="keyword">pid_t</span> <span class="title">getppid</span><span class="params">(<span class="keyword">void</span>)</span></span>; <span class="comment">// 父进程ID</span></span><br><span class="line"><span class="function"><span class="keyword">uid_t</span> <span class="title">getuid</span><span class="params">(<span class="keyword">void</span>)</span></span>;  <span class="comment">// 实际用户ID</span></span><br><span class="line"><span class="function"><span class="keyword">uid_t</span> <span class="title">geteuid</span><span class="params">(<span class="keyword">void</span>)</span></span>; <span class="comment">// 有效用户ID</span></span><br><span class="line"><span class="function"><span class="keyword">gid_t</span> <span class="title">getgid</span><span class="params">(<span class="keyword">void</span>)</span></span>;  <span class="comment">// 实际组ID</span></span><br><span class="line"><span class="function"><span class="keyword">git_t</span> <span class="title">getegid</span><span class="params">(<span class="keyword">void</span>)</span></span>; <span class="comment">// 有效组ID</span></span><br></pre></td></tr></table></figure><h3 id="8-3-函数-fork"><a href="#8-3-函数-fork" class="headerlink" title="8.3 函数 fork"></a>8.3 函数 fork</h3><p>一个现有的进程可以调用fork函数创建一个新进程。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 子进程返回0，父进程返回子进程ID，出错返回-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">pid_t</span> <span class="title">fork</span><span class="params">(<span class="keyword">void</span>)</span></span>;</span><br></pre></td></tr></table></figure><p>fork 函数被调用一次，但返回两次。进程ID 0总是由内核交换进程使用，所以一个子进程的进程ID不可能为0。</p><p>子进程和父进程继续执行fork调用之后的指令。子进程是父进程的副本，子进程获得父进程的数据空间、堆和栈的副本。注意，这是子进程所拥有的副本。父进程和子进程并不共享这些存储空间部分。父进程和子进程共享正文段。</p><p>写时复制（Copy-On-Write，COW）：由于fork之后经常跟随着exec，所以现在很多实现并不执行一个父进程数据段、栈和堆的完全副本。这些区域由父进程和子进程共享，并且内核将它们的访问权限改变为只读。如果父进程和子进程中的任一个试图修改这些区域，则内核只为修改区域的那块内存制作一个副本，通常是虚拟存储系统中的一“页”。</p><h5 id="文件共享"><a href="#文件共享" class="headerlink" title="文件共享"></a>文件共享</h5><p>在重定向父进程的标准输出时，子进程的标准输出也被重定向。实际上，fork的一个特性是父进程的所有打开文件描述符都被复制到子进程中。我们说“复制”是因为对每个文件描述符来说，就好像执行了dup函数。父进程和子进程每个相同的打开描述符共享一个文件表项，其文件偏移量是共享的。</p><p>fork之后处理文件描述有以下两种常见的情况：</p><ul><li>父进程等待子进程完成。在这种情况下，父进程无需对其描述符做任何处理。当子进程终止后，它曾进行读、写操作的任一共享描述符的文件偏移量已做了相应更新。</li><li>父进程和子进程各自执行不同的程序段。在这种情况下，在fork之后，父进程和子进程各自关闭它们不需使用的文件描述符，这样就不会干扰对方使用文件描述符。这种方法是网络服务进程经常使用的。</li></ul><p>除了打开文件之外，父进程的很多属性也由子进程继承，包括：</p><ul><li>实际用户ID、实际组ID、有效用户ID、有效组ID</li><li>附属组ID</li><li>进程组ID</li><li>会话ID</li><li>控制终端</li><li>这是用户ID标志和设置组ID标志</li><li>当前工作目录</li><li>根目录</li><li>文件模式创建屏蔽字</li><li>信号屏蔽和安排</li><li>对任一打开文件描述符的执行时关闭标志</li><li>环境</li><li>连接的共享存储段</li><li>存储映像</li><li>资源限制</li></ul><p>父进程和子进程之间的区别具体如下：</p><ul><li>fork的返回值不同</li><li>进程ID不同</li><li>这两个进程的父进程ID不同</li><li>子进程的tms_utime、tms_stime、tms_cutime和tms_ustime的值设置为0</li><li>子进程不继承父进程设置的文件锁</li><li>子进程的未处理闹钟被清除</li><li>子进程的未处理信号集被设置为空集</li></ul><h3 id="8-4-函数vfork"><a href="#8-4-函数vfork" class="headerlink" title="8.4 函数vfork"></a>8.4 函数vfork</h3><p>vfork函数用于创建一个新进程，而该新进程的目的是exec一个新程序。且vfork<strong>保证子进程先运行</strong>，在它调用exec或exit之后父进程才可能被调度运行，当子进程调用这两个函数中的任意一个时，父进程会恢复运行。（如果调用这两个函数之前子进程依赖父进程的进一步动作，则会导致死锁。）</p><p>vfork并不将父进程的地址空间完全复制到子进程中，因为子进程会立即调用exec（或exit），于是就不会引用该地址空间。不过在子进程调用exec或exit之前，它<strong>在父进程的空间中运行</strong>。因此，在子进程中改变fork之前的变量，会使得父进程中的变量更新。</p><h3 id="8-5-函数exit"><a href="#8-5-函数exit" class="headerlink" title="8.5 函数exit"></a>8.5 函数exit</h3><p>如果父进程在子进程之前终止，对于父进程已终止的所有进程，它们的父进程都改变为 init 进程。我们称这些进程由 init 进程收养。其操作过程大致是：在一个进程终止时，内核逐个检查所有活动进程，以判断它是否是正要终止进程的子进程，如果是，则该进程的父进程ID就更改为1。这种处理方式保证了每个进程有一个父进程。</p><p>如果子进程在父进程之前终止，那么父进程如何能在做相应检查时获得子进程的终止状态呢？如果子进程完全消失了，父进程在最终准备好检查子进程时是无法获取它的终止状态的。内核为每个终止子进程保存了一定量的信息，所以当终止进程的父进程调用wait或waitpid时，可以得到这些信息。这些信息至少包括进程ID、该进程的终止状态以及该进程使用的CPU时间总量。</p><p>一个已经终止、但是其父进程尚未对其进行善后处理（获取终止子进程的有关信息、释放它仍占用的资源）的进程被称为<strong>僵死进程</strong>。ps命令将僵死进程的状态打印为Z。</p><p>一个由init进程收养的进程终止时会发生什么？它会不会编程一个僵死进程？答案是不会。因为init被编写成无论何时只要有一个子进程终止，init就会调用一个wait函数取得其终止状态。这样也防止了系统中塞满僵死进程。</p><h3 id="8-6-函数wait和waitpid"><a href="#8-6-函数wait和waitpid" class="headerlink" title="8.6 函数wait和waitpid"></a>8.6 函数wait和waitpid</h3><p>当一个进程正常或异常终止时，内核就向其父进程发送SIGCHLD信号，该信号是内核向父进程发送的异步通知。对于这种信号系统默认动作是忽略它。</p><p>调用wait或waitpid的进程可能会发生什么：</p><ul><li>如果它没有任何子进程，则立即出错返回</li><li>如果其所有子进程还在运行，则阻塞</li><li>如果一个子进程已终止，正等待父进程获取其终止状态，则取得该子进程的终止状态立即返回。</li></ul><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/wait.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回进程ID；若出错，返回0或-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">pid_t</span> <span class="title">wait</span><span class="params">(<span class="keyword">int</span> *statloc)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">pid_t</span> <span class="title">waitpid</span><span class="params">(<span class="keyword">pid_t</span> pid, <span class="keyword">int</span> *statloc, <span class="keyword">int</span> options)</span></span>;</span><br></pre></td></tr></table></figure><ul><li><p>在一个子进程终止前，wait使其调用者阻塞，而waitpid有一选项，可使调用者不阻塞。</p></li><li><p>waitpid并不等待在其调用之后的第一个终止子进程，它有若干个选项，可以控制它所等待的进程。</p></li></ul><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210519102354783.png" alt="image-20210519102354783"></p><p>以上是用于检查wait获取的statloc的宏。</p><p>如果一个父进程有几个子进程，那么只要一个子进程终止，wait就返回。waitpid函数提供了等待一个特定进程的功能。对于waitpid中的pid参数：</p><ul><li>-1：等待任一子进程，此时与wait等效</li><li>>0：等待进程ID与pid相等的子进程</li><li>==0：等待组ID等于调用组进程ID的任意子进程</li><li>pid&lt;-1：等待组ID等于pid绝对值的任一子进程</li></ul><p>对于wait，其唯一的出错就是调用进程没有子进程。但是对于waitpid，如果指定的进程或进程组不存在，或者参数pid指定的进程不是调用进程的子进程，都可能出错。</p><p>waitpid的options参数使我们能进一步控制waitpid的操作。此参数或者为0，或者为下图中常量按位或运算的结果。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210519104255579.png" alt="image-20210519104255579"></p><p>如果一个进程fork一个子进程，但不要等待子进程终止，也不希望子进程处于僵死状态直到父进程结束，实现这一要求的诀窍是调用fork两次，让第一个子进程先终止并等待，使得第二个子进程的父进程变成init，而不等待第二个子进程。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;apue.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/wait.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">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="keyword">pid_t</span> pid;</span><br><span class="line">    <span class="keyword">if</span> ((pid = fork()) &lt; <span class="number">0</span>)</span><br><span class="line">        err_sys(<span class="string">&quot;fork error&quot;</span>);</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (pid == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> ((pid = fork()) &lt; <span class="number">0</span>)</span><br><span class="line">            err_sys(<span class="string">&quot;fork error&quot;</span>);</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (pid &gt; <span class="number">0</span>)</span><br><span class="line">            <span class="built_in">exit</span>(<span class="number">0</span>);  <span class="comment">// 第一个子进程结束，该子进程的子进程被init进程收养，而不会由于未等待其结束变成僵尸进程。</span></span><br><span class="line"></span><br><span class="line">        sleep(<span class="number">2</span>);</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;second child, parent pid = %ld\n&quot;</span>, (<span class="keyword">long</span>)getppid());</span><br><span class="line">        <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (waitpid(pid, <span class="literal">NULL</span>, <span class="number">0</span>) != pid) <span class="comment">// 等待第一个子进程结束</span></span><br><span class="line">        err_sys(<span class="string">&quot;waitpid error&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="8-7-函数waitid"><a href="#8-7-函数waitid" class="headerlink" title="8.7 函数waitid"></a>8.7 函数waitid</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/wait.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0；若出错，返回-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">waitid</span><span class="params">(<span class="keyword">idtype_t</span> idtype, <span class="keyword">id_t</span> id, <span class="keyword">siginfo_t</span> *infop, <span class="keyword">int</span> options)</span></span>;</span><br></pre></td></tr></table></figure><p>与waitpid相似，waitid允许一个进程指定要等待的子进程。</p><h3 id="8-8-函数wait3和wait4"><a href="#8-8-函数wait3和wait4" class="headerlink" title="8.8 函数wait3和wait4"></a>8.8 函数wait3和wait4</h3><p>wait3、wait4 比wait、waitpid、waitid所提供的功能要多一个，这与附加参数有关。该参数允许内核返回由终止进程及其所有子进程使用的资源概况。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/types.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/wait.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/time.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/resource.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回进程ID；若出错，返回-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">pid_t</span> <span class="title">wait3</span><span class="params">(<span class="keyword">int</span> *statloc, <span class="keyword">int</span> options, struct rusage *rusage)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">pid_t</span> <span class="title">wait4</span><span class="params">(<span class="keyword">pid_t</span> pid, <span class="keyword">int</span> *statloc, <span class="keyword">int</span> options, struct rusage *rusage)</span></span>;</span><br></pre></td></tr></table></figure><h3 id="8-9-竞争条件"><a href="#8-9-竞争条件" class="headerlink" title="8.9 竞争条件"></a>8.9 竞争条件</h3><p>当各进程都企图对共享数据进行某种处理，而最后的结果又取决于进程运行的顺序时，我们认为这发生了<strong>竞争条件（race condition）</strong>。</p><p>如果一个父进程要等待其子进程终止，使用wait函数中的一个。</p><p>如果一个子进程要等待其父进程终止，则可以使用<strong>轮询</strong>：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span> (getppid() != <span class="number">1</span>)</span><br><span class="line">    sleep(<span class="number">1</span>);</span><br></pre></td></tr></table></figure><p>但这种方式浪费了时间。为避免竞争条件和轮询，可使用信号机制或IPC。</p><h3 id="8-10-函数exec"><a href="#8-10-函数exec" class="headerlink" title="8.10 函数exec"></a>8.10 函数exec</h3><p>exec并不创建新程序，所以前后的进程ID并未改变，exec只是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆段和栈段。</p><p>基本进程控制原语：</p><ul><li>fork：创建进程</li><li>exec：初始执行新的程序</li><li>exit：处理终止</li><li>wait：等待终止</li></ul><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * 7个函数，若出错，返回-1；若成功，不返回</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">execl</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *pathname, <span class="keyword">const</span> <span class="keyword">char</span> *arg0, ... <span class="comment">/* (char *)0 */</span>)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">execv</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *pathname, <span class="keyword">char</span> *<span class="keyword">const</span> argv[])</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">ececle</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *pathname, <span class="keyword">const</span> <span class="keyword">char</span> *arg0, ..., <span class="comment">/* (char *)0, char *const envp[] */</span>)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">execve</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *pathname, <span class="keyword">char</span> *<span class="keyword">const</span> argv[], <span class="keyword">char</span> *<span class="keyword">const</span> envp[])</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">execlp</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *filename, <span class="keyword">const</span> <span class="keyword">char</span> *arg0, ... <span class="comment">/* (char *) 0 */</span>)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">ececvp</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *filename, <span class="keyword">char</span> *<span class="keyword">const</span> argv[])</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fexecve</span><span class="params">(<span class="keyword">int</span> fd, <span class="keyword">char</span> *<span class="keyword">const</span> argv[], <span class="keyword">char</span> *<span class="keyword">const</span> envp[])</span></span>;</span><br></pre></td></tr></table></figure><p>前四个以路径名作为参数，后两个以文件名作为参数，最后一个以文件描述符作为参数。当以文件名作为参数时：</p><ul><li>如果filename包含/，则就将其视为路径名pathname。</li><li>否则，按PATH环境变量，在个目录下搜索可执行文件</li></ul><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210519123453378.png" alt="image-20210519123453378"></p><p>简记：</p><ul><li>l：表示列表，命令的参数分开传递，以(char *)0结尾</li><li>v：表示矢量，命令的参数使用char *数组传递</li><li>e：传递环境变量</li><li>p：使用filename而不是路径名</li><li>f：使用文件描述符</li></ul><p>前面曾提及，在执行exec后，进程ID没有改变。但新程序从调用进程继承了的下列属性：</p><ul><li>进程ID和父进程ID</li><li>实际用户ID和实际组ID</li><li>附属组ID</li><li>进程组ID</li><li>会话ID</li><li>控制终端</li><li>闹钟尚余留的时间</li><li>当前工作目录</li><li>根目录</li><li>文件模式创建屏蔽字</li><li>文件锁</li><li>进程信号屏蔽</li><li>未处理信号</li><li>资源限制</li><li>nice值</li><li>tms_utime、tms_stime、tms_cutime以及tms_cstime值</li></ul><p>对打开文件的处理与每个描述符的执行时关闭（close-on-exec）标志值有关。FD_CLOEXEC标志，进程中每个打开描述符都有一个执行时关闭标志。若设置了此标志，则在执行exec时关闭该描述符；否则该描述符任然打开。除非特地用fcntl设置了该执行时关闭标志，否则系统的默认操作是在exec后仍保持这种描述符打开。对于目录流，明确要求exec时关闭。</p><p>在exec前后实际用户ID和实际组ID保持不变，而有效ID是否改变则取决于所执行程序文件的设置用户ID和设置组ID位是否设置。如果新程序的设置用户ID位已设置，则有效用户ID变为程序文件所有者的ID；否则有效用户ID不变。对组ID的处理方式与此相同。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210519114732281.png" alt="image-20210519114732281"></p><p>7个函数中只有execve是系统调用，其他6个是库函数。</p><h3 id="8-11-更改用户ID和更改组ID"><a href="#8-11-更改用户ID和更改组ID" class="headerlink" title="8.11 更改用户ID和更改组ID"></a>8.11 更改用户ID和更改组ID</h3><p>UNIX系统中，特权以及访问控制是基于用户ID和组ID的。一般而言，在设计应用时，我们总是试图使用<strong>最小特权</strong>模型。</p><p>可使用setuid函数设置实际用户ID和有效用户ID，用setgid函数设置实际组ID和有效组ID。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">setuid</span><span class="params">(<span class="keyword">uid_t</span> uid)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">setgid</span><span class="params">(<span class="keyword">gid_t</span> gid)</span></span>;</span><br></pre></td></tr></table></figure><p>更改用户ID的规则：</p><ul><li>若进程具有超级用户特权，则 setuid 函数将实际用户ID、有效用户ID以及保存的设置用户ID设置为 uid。</li><li>若进程没有超级用户特权，但是 uid 等于实际用户ID或保存的设置用户ID，则 setuid 只将有效用户ID设置为 uid。不更改实际用户ID和保存的设置用户ID。</li><li>如果上面两个条件都不满足，则 errno 设置为 EPERM，并返回-1。</li></ul><p>关于内核维护的3个用户ID，还需要注意以下几点：</p><ul><li>只有超级用户进程可以更改实际用户ID。通常，实际用户ID是在用户登陆时，由login程序设置的，而且决不会改变它。因为login是一个超级用户进程，当它调用 setuid 时，设置所有3个用户ID。</li><li>仅当对程序文件设置了设置用户ID位时，exec 函数才设置有效用户ID。如果设置用户ID位没有设置，exec函数不会改变有效用户ID，而将维持现有值。任何时候都可以调用setuid，将有效用户ID设置为实际用户ID或保存的设置用户ID。自然地，不能将有效用户ID设置为任一随机值。</li><li>保存的设置用户ID是由exec复制有效用户ID而得到的。如果设置了文件的设置用户ID，则在exec根据文件的用户ID设置了进程的有效ID后，这个副本就被保存起来了。</li></ul><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210520090734802.png" alt="image-20210520090734802"></p><h5 id="函数-setreuid-和-setregid"><a href="#函数-setreuid-和-setregid" class="headerlink" title="函数 setreuid 和 setregid"></a>函数 setreuid 和 setregid</h5><p>setreuid用于交换实际用户ID和有效用户ID的值。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0；’若出错，返回-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">setreuid</span><span class="params">(<span class="keyword">uid_t</span> ruid, <span class="keyword">uid_t</span> euid)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">setregid</span><span class="params">(<span class="keyword">gid_t</span> rgid, <span class="keyword">git_t</span> egid)</span></span>;</span><br></pre></td></tr></table></figure><p>若其中任一参数值为-1，则表示相应的ID应当保持不变。</p><p>规则很简单：一个非特权用户总能交换实际用户ID和有效用户ID。这就允许一个设置用户ID程序交换成用户的普通权限，以后又可再次交换回设置用户ID权限。</p><h5 id="函数-seteuid-和-setegid"><a href="#函数-seteuid-和-setegid" class="headerlink" title="函数 seteuid 和 setegid"></a>函数 seteuid 和 setegid</h5><p>只更改有效用户ID和有效组ID。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0；若出错，返回-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">seteuid</span><span class="params">(<span class="keyword">uid_t</span> uid)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">setegid</span><span class="params">(<span class="keyword">gid_t</span> gid)</span></span>;</span><br></pre></td></tr></table></figure><p>一个非特权用户可将其有效用户ID设置为其实际用户ID或其保存的设置用户ID。对于一个特权用户，则可以将有效用户ID设置为uid。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210520092416680.png" alt="image-20210520092416680"></p><h3 id="8-13-函数system"><a href="#8-13-函数system" class="headerlink" title="8.13 函数system"></a>8.13 函数system</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">system</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *cmdstring)</span></span>;</span><br></pre></td></tr></table></figure><p>如果cmdstring是一个空指针，仅当系统支持system函数时，返回非0值。这一特征用来检测在给定的操作系统上是否支持system函数。</p><p>system在其实现中调用了fork、exec和waitpid，因此有3中返回值:</p><ul><li>fork失败或者waitpid返回除EINTR之外的出错，则system返回-1，并且设置errno以指示错误类型。</li><li>如果exec失败，则其返回值如同shell执行了<code>exit(127)</code>。</li><li>否则所有3个函数都成功，那么system的返回值是shell的终止状态。</li></ul><h5 id="设置用户ID程序"><a href="#设置用户ID程序" class="headerlink" title="设置用户ID程序"></a>设置用户ID程序</h5><p>如果在一个设置用户ID程序中调用system，那会发生什么呢？这是一个安全方面的漏洞，决不应当这样做。</p><h3 id="8-17-进程时间"><a href="#8-17-进程时间" class="headerlink" title="8.17 进程时间"></a>8.17 进程时间</h3><p>在1.10节中我们介绍了可以度量的3个时间：时钟时间、用户CPU时间和系统CPU时间。任一进程都可调用times函数获得它自己以及已终止子进程的上述值。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/times.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回流逝的墙上时钟时间（以时钟滴答数为单位）；若出错，返回-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">clock_t</span> <span class="title">times</span><span class="params">(struct tms *buf)</span></span>;</span><br></pre></td></tr></table></figure><p>此函数填写由 <em>buf</em> 指向的tms结构，该结构定义如下：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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="class"><span class="keyword">struct</span> <span class="title">tms</span> &#123;</span></span><br><span class="line">    <span class="keyword">clock_t</span> tms_utime;  <span class="comment">/* user CPU time */</span></span><br><span class="line">    <span class="keyword">clock_t</span> tms_stime;  <span class="comment">/* system CPU time */</span></span><br><span class="line">    <span class="keyword">clock_t</span> tms_cutime; <span class="comment">/* user CPU time, terminated children */</span></span><br><span class="line">    <span class="keyword">clock_t</span> tms_cstime; <span class="comment">/* system CPU time, terminated children */</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>注意，此结构没有包含墙上时钟时间，times函数返回墙上时钟时间作为其函数值。此值是相对于过去的某一时刻度量的，所以不能用绝对值而必须使用其相对值，即两次调用times，用两次返回的差值作为墙上时钟时间。</p><p>所有由此函数返回的clock_t值都用_SC_CLK_TCK转换成秒数。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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="meta-keyword">include</span> <span class="meta-string">&quot;apue.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/times.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">pr_times</span><span class="params">(<span class="keyword">clock_t</span>, struct tms*, struct tms*)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">do_cmd</span><span class="params">(<span class="keyword">char</span> *)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">char</span> *argv[])</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> i;</span><br><span class="line">    setbuf(<span class="built_in">stdout</span>, <span class="literal">NULL</span>);</span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">1</span>; i &lt; argc; i++)</span><br><span class="line">        do_cmd(argv[i]);</span><br><span class="line">    <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">do_cmd</span><span class="params">(<span class="keyword">char</span> *cmd)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">tms</span> <span class="title">tmsstart</span>, <span class="title">tmsend</span>;</span></span><br><span class="line">    <span class="keyword">clock_t</span> start, end;</span><br><span class="line">    <span class="keyword">int</span> status;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;\ncommand: %s\n&quot;</span>, cmd);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> ((start = times(&amp;tmsstart)) == <span class="number">-1</span>)</span><br><span class="line">        err_sys(<span class="string">&quot;times_error&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> ((status = system(cmd)) &lt; <span class="number">0</span>)</span><br><span class="line">        err_sys(<span class="string">&quot;system() error&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> ((end = times(&amp;tmsend)) == <span class="number">-1</span>)</span><br><span class="line">        err_sys(<span class="string">&quot;times error&quot;</span>);</span><br><span class="line"></span><br><span class="line">    pr_times(end-start, &amp;tmsstart, &amp;tmsend);</span><br><span class="line">    pr_exit(status);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">pr_times</span><span class="params">(<span class="keyword">clock_t</span> real, struct tms *tmsstart, struct tms *tmsend)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">long</span> clktck = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (clktck == <span class="number">0</span>)</span><br><span class="line">        <span class="keyword">if</span> ((clktck = sysconf(_SC_CLK_TCK)) &lt; <span class="number">0</span>) <span class="comment">// 获取转换进制</span></span><br><span class="line">            err_sys(<span class="string">&quot;sysconf error&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;  real:  %7.2f\n&quot;</span>, real / (<span class="keyword">double</span>)clktck);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;  user:  %7.2f\n&quot;</span>, (tmsend-&gt;tms_utime - tmsstart-&gt;tms_utime) / (<span class="keyword">double</span>)clktck);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;  sys:   %7.2f\n&quot;</span>, (tmsend-&gt;tms_stime - tmsstart-&gt;tms_stime) / (<span class="keyword">double</span>)clktck);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;  child user:  %7.2f\n&quot;</span>, (tmsend-&gt;tms_cutime - tmsstart-&gt;tms_cutime) / (<span class="keyword">double</span>)clktck);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;  child sys:   %7.2f\n&quot;</span>, (tmsend-&gt;tms_cstime - tmsstart-&gt;tms_cstime) / (<span class="keyword">double</span>)clktck);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210520104208845.png" alt="image-20210520104208845"></p><h2 id="第9章-进程关系"><a href="#第9章-进程关系" class="headerlink" title="第9章 进程关系"></a>第9章 进程关系</h2><h3 id="9-1-引言"><a href="#9-1-引言" class="headerlink" title="9.1 引言"></a>9.1 引言</h3><p>本章将更详细地说明进程组以及会话的概念，还将介绍登陆shell和所有从登陆shell启动的进程之间的关系。</p><h3 id="9-2-终端登陆"><a href="#9-2-终端登陆" class="headerlink" title="9.2 终端登陆"></a>9.2 终端登陆</h3><p>先说明当我们登陆到UNIX系统时所执行的各个程序。终端或者是本地的，或者是远程的，登陆都经由内核中的终端设备驱动程序。因为连接到主机上的终端设备数是固定的，所以同时的登陆数也就有了已知的上限。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210520110057746.png" alt="image-20210520110057746"></p><p>系统管理者创建通常名为/etc/ttys的文件，其中，每个终端设备都有一行，每一行说明设备名和传到getty程序的参数。当系统自举时，内核创建进程ID为1的init进程。init进程是系统进入多用户模式。init读取文件/etc/ttys，对每一个允许登陆的终端设备，init调用一次fork，它所生成的子进程则exec getty程序。图9-1中所有进程的实际用户ID和有效用户ID都是0，init以空环境exec getty程序。</p><p>getty对终端设备调用open函数，以读、写方式将终端打开。如果设备是调制解调器，则open可能会在设备驱动程序中滞留，直到用户拨号调制解调器，并且线路被接通。一旦设备被打开，则文件描述符0、1、2就被设置到该设备。然后getty输出“login：”之类的信息，并等待用户键入用户名。</p><p>当用户键入用户名后，getty的工作就完成了。然后它以类似于下列的方式调用login程序：</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">execle(<span class="string">&quot;/bin/login&quot;</span>, <span class="string">&quot;login&quot;</span>, <span class="string">&quot;-p&quot;</span>, username, (<span class="keyword">char</span>*)<span class="number">0</span>, envp);</span><br></pre></td></tr></table></figure><p>init以一个空环境调用getty，getty以终端名和在gettytab中说明的环境字符串为login创建一个环境（envp参数）。-p标志通知login保留传递给它的环境，也可将其他环境字串加到该环境中，但是不要替换它。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210520110806060.png" alt="image-20210520110806060"></p><p>因为最初的init进程具有超级用户权限，所以图9-2中的所有进程都有超级用户权限。图9-2中底部3个进程的进程ID相同，因为进程ID不会因执行exec而改变。并且，除了最初的init进程，所有进程的父进程ID都是1。</p><p>login能处理多项工作。因为它得到了用户名，所以能调用getpwnam取得相应的口令文件登录项。然后getpass以显示提示“Password：”，接着读用户键入的口令。它调用crypt将用户键入的口令加密，并与该用户在阴影口令文件中登录想的pw_passwd字段相比较。如果用户几次键入的口令都无效，则login以参数l调用exit表示登陆过程失败。父进程（init）了解到子进程的终止情况后，将再次调用fork，其后又执行了getty，对终端重复上述过程。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210520111457340.png" alt="image-20210520111457340"></p><p>如果用户登陆正确，login就将完成如下工作：</p><ul><li><p>将当前工作目录更改为该用户的起始目录</p></li><li><p>调用chown更改该终端的所有权，使登陆用户成为它的所有者</p></li><li><p>将对该终端设备的访问权限改变成“用户读和写”</p></li><li><p>调用setgid及initgroups设置进程的组ID</p></li><li><p>用login得到的所有信息初始化环境：起始目录（HOME）、shell（SHELL）、用户名（USER和LOGNAME）以及一个系统默认路径（PATH）</p></li><li><p>login进程更改登陆用户的用户ID（setuid）并调用该用户的登陆shell，其方式类似于：</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">execl(<span class="string">&quot;/bin/sh&quot;</span>, <span class="string">&quot;-sh&quot;</span>, (<span class="keyword">char</span> *)<span class="number">0</span>);</span><br></pre></td></tr></table></figure><p>argv[0]的第一个字符负号“-”是一个标志，表示该shell被作为登陆shell调用。</p></li></ul><p>至此，登陆用户的登陆shell开始运行。</p><h3 id="9-3-网络登陆"><a href="#9-3-网络登陆" class="headerlink" title="9.3 网络登陆"></a>9.3 网络登陆</h3><p>网络登陆时，在终端和计算机之间的连接不再是点到点的。在网络登陆情况下，login仅仅是一种可用的服务，这与其他网络服务（如FTP或SMTP）的性质相同。所有登录都经由内核的网络接口驱动程序（如以太网驱动程序），并且事先并不知道将会有多少这样的登陆。</p><p>系统使用<strong>伪终端</strong>的软件驱动程序，仿真串行终端的运行行为，并将终端操作映射为网络操作。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210520112553978.png" alt="image-20210520112553978"></p><p>作为系统启动的一部分，init调用一个shell，使其执行shell脚本/etc/rc，由此shell脚本启动一个守护进程inetd。一旦此shell脚本终止，inetd的父进程就变成init。inetd等待TCP/IP连接请求到达主机，而当一个连接请求到达时，它执行一次fork，然后生成的子进程exec适当的程序。</p><p>telnetd进程打开一个伪终端设备，并用fork分成两个进程。父进程处理网络连接的通信，子进程则执行login程序。父进程和子进程通过伪终端相连接。在调用exec之前，子进程使用其文件描述符0、1、2与伪终端相连。如果登陆正确，login就执行9.2节中所述的同样步骤——更改当前工作目录为起始目录、设置登陆用户的组ID、用户ID以及初始环境。然后login调用exec将其自身替换为登陆用户的登陆shell。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210520113722329.png" alt="image-20210520113722329"></p><p>需要理解的重点是：当通过终端或网络登陆时，我们得到一个登陆shell，其标准输入、标准输出和标准错误要么连接到一个终端设备，要么连接到一个伪终端设备上。这一登陆shell是一个会话的开始，而此终端或伪终端则是会话的控制终端。</p><h3 id="9-4-进程组"><a href="#9-4-进程组" class="headerlink" title="9.4 进程组"></a>9.4 进程组</h3><p>进程组是一个或多个进程的集合，通常，它们是在同一作业中结合起来的，同一进程组中的各进程接受来自同一终端的各种信号。每个进程组有一个唯一的进程组ID。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 返回调用进程的进程组ID</span></span><br><span class="line"><span class="function"><span class="keyword">pid_t</span> <span class="title">getpgrp</span><span class="params">(<span class="keyword">void</span>)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 若成功，返回进程组ID；若出错，返回-1</span></span><br><span class="line"><span class="function"><span class="keyword">pid_t</span> <span class="title">getpgid</span><span class="params">(<span class="keyword">pid_t</span> pid)</span></span>;</span><br></pre></td></tr></table></figure><p>getpgid(0)等效于getpgrp()。</p><p>每个进程组都有一个组长进程。组长进程的进程组ID等于其进程ID。</p><p>进程组组长可以创建一个进程组、创建该组中的进程，然后终止。只要在某个进程组中有一个进程存在，则该进程组就存在，这与其组长进程是否终止无关。从进程组创建开始到其中最后一个进程离开为止的时间称为进程组的生命期。某个进程组中的最后一个进程可以终止，也可以转移到另一个进程组。</p><p>进程调用setpgid可以加入一个现有的进程组或者创建一个新进程组。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0；若出错，返回-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">setpgid</span><span class="params">(<span class="keyword">pid_t</span> pid, <span class="keyword">pid_t</span> pgid)</span></span>;</span><br></pre></td></tr></table></figure><p>setpgid函数将pid进程的进程组ID设置为pgid。如果这两个参数相等，则由pid指定的进程变成进程组组长。如果pid是0，则使用调用者的进程ID。另外，如果pgid是0，则由pid指定的进程ID用作进程组ID。一个进程只能为它自己或它的子进程设置进程组ID。在它的子进程调用了exec后，它就不再更改该子进程的进程组ID。</p><h3 id="9-5-会话"><a href="#9-5-会话" class="headerlink" title="9.5 会话"></a>9.5 会话</h3><p>会话（session）是一个或多个进程组的集合。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210520122749232.png" alt="image-20210520122749232"></p><p>通常是由shell的管道将几个进程编成一组的。上图所示的安排可能是由下列形式的shell命令形成的：</p><figure class="highlight shell"><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="meta">$</span><span class="bash"> proc1 | proc2 &amp;</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> proc3 | proc4 | proc5</span></span><br></pre></td></tr></table></figure><p>进程调用setsid函数建立一个新会话：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回进程组ID；若出错，返回-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">pid_t</span> <span class="title">setsid</span><span class="params">(<span class="keyword">void</span>)</span></span>;</span><br></pre></td></tr></table></figure><p>如果调用此函数的进程不是一个进程组的组长，则函数创建一个新会话，具体会发生以下三件事：</p><ul><li>该进程变成新会话的首进程。此时，该进程是新会话中的唯一进程。</li><li>该进程成为一个新进程组的组长进程。新进程组ID是该调用进程的进程ID。</li><li>该进程没有控制终端。如果在调用setsid之前该进程有一个控制终端，那么这种联系也被切断。</li></ul><p>如果该调用进程已经是一个进程组的组长，则此函数返回出错。为了保证不处于这种情况，通常先调用fork，然后使其父进程终止，而子进程则继续。因为子进程继承了父进程的进程组ID，而其进程ID则是新分配的，两者不可能相等，这就保证了子进程不是一个进程组的组长。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回会话首进程的进程组ID；若出错，返回-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">pid_t</span> <span class="title">getsid</span><span class="params">(<span class="keyword">pid_t</span> pid)</span></span>;</span><br></pre></td></tr></table></figure><p>如若pid是0，getsid返回调用进程的会话首进程的进程组ID。如果pid并不属于调用者所在的会话，那么调用进程就不能得到该会话首进程的进程组ID。</p><h3 id="9-6-控制终端"><a href="#9-6-控制终端" class="headerlink" title="9.6 控制终端"></a>9.6 控制终端</h3><p>会话和进程组还有一些其他特性：</p><ul><li>一个会话可以有一个<em>控制终端</em>。这通常是终端设备或伪终端内设备。</li><li>建立与控制终端连接的会话首进程被称为控制进程。</li><li>一个会话中的几个进程组可以被分成一个前台进程组以及一个或多个后台进程组。</li><li>如果一个会话有一个控制终端，则它有一个前台进程组，其他进程组为后台进程组。</li><li>无论何时键入终端的中断键，都会将中断信号发送至前台进程组的所有进程。</li><li>无论何时键入终端的退出键，都会将退出信号发送至前台进程组的所有进程。</li><li>无论终端接口检测到调制解调器（或网络）已经断开，则将挂断信号发送至控制进程（会话首进程）。</li></ul><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210521085957751.png" alt="image-20210521085957751"></p><p>登陆时，将自动建立控制终端。</p><h3 id="9-8-作业控制"><a href="#9-8-作业控制" class="headerlink" title="9.8 作业控制"></a>9.8 作业控制</h3><p>作业控制允许在一个终端上启动多个作业（进程组），它控制哪一个作业可以访问该终端以及哪些作业在后台运行。作业控制要求以下3种形式的支持：</p><ul><li>支持作业控制的shell</li><li>内核中的终端驱动程序必须支持作业控制</li><li>内核必须提供对某些作业控制信号的支持</li></ul><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210521090709179.png" alt="image-20210521090709179"></p><h2 id="第10章-信号"><a href="#第10章-信号" class="headerlink" title="第10章 信号"></a>第10章 信号</h2><h3 id="10-1-引言"><a href="#10-1-引言" class="headerlink" title="10.1 引言"></a>10.1 引言</h3><p>信号是软件中断。信号提供了一种处理异步事件的方法。</p><h3 id="10-2-信号概念"><a href="#10-2-信号概念" class="headerlink" title="10.2 信号概念"></a>10.2 信号概念</h3><p>每个信号有一个名字，这些名字都以3个字符SIG开头，如：</p><ul><li>SIGABRT：夭折信号，当进程调用abort函数时产生这种信号</li><li>SIGALRM：闹钟信号，由alarm函数设置定时器超时后将产生此信号</li></ul><p>头文件<code>signal.h</code>中，信号名都被定义为<strong>正整数常量</strong>。不存在编号为0的信号，POSIX.1将此信号编号值称为空信号。</p><p>信号产生：</p><ul><li>终端产生（Ctrl+C）</li><li>硬件异常（除以0）</li><li>kill函数或kill命令</li><li>软件条件（管道读进程已终止后，一个进程写此管道）</li></ul><p>信号处理方式：</p><ul><li>忽略此信号：大多数信号都可用这种方式处理，但有两种信号却绝不能被忽略：SIGKILL和SIGSTOP。</li><li>捕捉信号：为做到这一点，要通知内核在某种信号发生时，调用一个用户函数，执行事件处理。</li><li>执行系统默认动作：大多数信号的系统默认动作是终止该进程。</li></ul><h3 id="10-3-函数signal"><a href="#10-3-函数signal" class="headerlink" title="10.3 函数signal"></a>10.3 函数signal</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;signal.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回以前的信号处理配置；若出错，返回SIG_ERR */</span></span><br><span class="line"><span class="keyword">void</span> (*signal(<span class="keyword">int</span> signo, <span class="keyword">void</span> (*func)(<span class="keyword">int</span>)))(<span class="keyword">int</span>);</span><br></pre></td></tr></table></figure><p>signo是上一节所说的信号名。func的值是常量SIG_IGN、常量SIG_DFL或当接到此信号后要调用的函数的地址，该函数被称为<strong>信号处理程序</strong>或<strong>信号捕捉函数</strong>。</p><p>signal函数返回一个函数指针，而该指针所指向的函数无返回值，带一个整型参数。</p><p>上述声明太复杂，下面是简化的声明：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">typedef</span> <span class="keyword">void</span> <span class="title">Sigfunc</span><span class="params">(<span class="keyword">int</span>)</span></span>;</span><br><span class="line"><span class="function">Sigfunc *<span class="title">signal</span><span class="params">(<span class="keyword">int</span>, Sigfunc *)</span></span>;</span><br></pre></td></tr></table></figure><p>当执行一个程序时，所有信号的状态都是系统默认或忽略。通常，所有信号都被设置为它们的默认动作，除非调用exec的进程忽略该信号。确切地讲，exec函数将原先设置为要捕捉的信号都更改为默认动作，其他信号的状态则不变（一个进程原先要捕捉的信号，当其执行一个新程序后，就不能再捕捉了，因为信号捕捉函数的地址很可能在所执行的新程序文件中已无意义）。</p><p>signal函数的限制：不改变信号的处理方式就不能确定信号的当前处理方式。</p><h3 id="10-4-不可靠信号"><a href="#10-4-不可靠信号" class="headerlink" title="10.4 不可靠信号"></a>10.4 不可靠信号</h3><p>早期的UNIX版本中，信号是不可靠的，信号可能会丢失：一个信号发生了，但进程却可能不知道这一点。同时，进程对信号的控制能力也很差，它能捕捉信号或忽略它。</p><p>有时用户希望通知内核阻塞某个信号：不要忽略该信号，在其发生时记住它，然后在进程做好了准备时再通知它。这种阻塞信号的能力当时并不具备。</p><h3 id="10-5-中断的系统调用"><a href="#10-5-中断的系统调用" class="headerlink" title="10.5 中断的系统调用"></a>10.5 中断的系统调用</h3><p>早期UNIX系统的一个特性是：如果进程在执行一个低速系统调用而阻塞期间捕捉到一个信号，则该系统调用就被中断不再继续执行。该系统调用返回出错，其errno设置为EINTR。这样处理是因为一个信号发生了，进程捕捉到它，这意味着已经发生了某些事情，所以是个好机会应当唤醒阻塞的系统调用。</p><p>低速系统调用：可能会使进程永远阻塞的一类系统调用。</p><p>与被中断的系统调用相关的问题是必须显式地处理出错返回：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">again:</span><br><span class="line"><span class="keyword">if</span> ((n = read(fd, buf, BUFFSIZE)) &lt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (errno = EINTR)</span><br><span class="line">            <span class="keyword">goto</span> again;  <span class="comment">/* just an interrupted system call */</span></span><br><span class="line">        <span class="comment">/* handle other errors */</span></span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>为帮助应用程序使其不必处理被中断地系统调用，引进了某些被中断系统调用的自动重启动：ioctl、read、readv、write、writev、wait和waitpid。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210521102217768.png" alt="image-20210521102217768"></p><h3 id="10-6-可重入函数"><a href="#10-6-可重入函数" class="headerlink" title="10.6 可重入函数"></a>10.6 可重入函数</h3><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210521102709874.png" alt="image-20210521102709874"></p><p>没有列入上表的大多数函数是不可重入的。</p><h3 id="10-8-可靠信号术语和语义"><a href="#10-8-可靠信号术语和语义" class="headerlink" title="10.8 可靠信号术语和语义"></a>10.8 可靠信号术语和语义</h3><p>当造成信号的事件发生时，为进程产生一个信号，这个过程叫做信号<strong>产生</strong>。信号产生时，内核通常在进程表中以某种形式设置一个标志，这个过程叫做信号<strong>递送</strong>。</p><p>在信号产生和递送之间的时间间隔内，称信号是未决的（pending）。</p><p>进程可以选用“阻塞信号调用”。如果为进程产生了一个阻塞的信号，而且对该信号的动作是系统默认动作或捕捉该信号，则为该进程将此信号保持为未决状态，直到该进程对此信号解除了阻塞，或者将对此信号的动作更改为忽略。内核在递送一个原来被阻塞的信号给进程时，才决定对它的处理方式。于是进程在信号递送给它之前仍可改变对该信号的动作。</p><p>进程调用sigpending函数来判定哪些信号是设置为阻塞并处于未决状态的。</p><h3 id="10-9-函数kill和raise"><a href="#10-9-函数kill和raise" class="headerlink" title="10.9 函数kill和raise"></a>10.9 函数kill和raise</h3><p>kill函数将信号发送给进程或进程组。raise函数则允许进程向自身发送信号。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;signal.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0；若出错，返回-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">kill</span><span class="params">(<span class="keyword">pid_t</span> pid, <span class="keyword">int</span> signo)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">raise</span><span class="params">(<span class="keyword">int</span> signo)</span></span>;</span><br></pre></td></tr></table></figure><p>调用raise(signo)等价于调用kill(getpid(), signo)。</p><p>kill的pid参数有以下4种不同的情况</p><ul><li>pid &gt; 0：将该信号发送给进程ID为pid的进程</li><li>pid == 0：将该信号发送给与发送进程属于同一进程组的所有进程（这些进程的进程组ID等于发送进程的进程组ID），而且发送进程具有权限向这些进程发送信号。</li><li>pid &lt; 0：将该信号发送给其他进程组ID等于pid绝对值，而且发送进程具有权限向其发送信号的所有进程。（所有进程不包括系统进程集中的进程）</li><li>pid == -1：将该信号发送给发送进程有权限向他们发送信号的所有进程。（所有进程并不包括系统进程集中的进程）</li></ul><p>如上所述，进程将信号发送给其他进程需要权限。</p><p>POSIX.1将信号编号为0定义为空信号。如果signo参数是0，则kill仍执行正常的错误检查，但不发送信号。这常被用来确定一个特定进程是否仍然存在。如果向一个并不存在的进程发送空信号，则kill返回-1，errno被设置为ESRCH。</p><h3 id="10-10-函数alarm和pause"><a href="#10-10-函数alarm和pause" class="headerlink" title="10.10 函数alarm和pause"></a>10.10 函数alarm和pause</h3><p>alarm函数可以设置一个定时器，在将来的某个时刻该定时器会超时。当定时器超时时，产生SIGALRM信号。如果忽略或不捕捉信号，则其默认动作是终止调用该alarm函数的进程。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 返回0或以前设置的闹钟时间的余留秒数 */</span></span><br><span class="line"><span class="function"><span class="keyword">unsigned</span> <span class="keyword">int</span> <span class="title">alarm</span><span class="params">(<span class="keyword">unsigned</span> <span class="keyword">int</span> seconds)</span></span>;</span><br></pre></td></tr></table></figure><p>每个进程只能有一个闹钟时间。如果在调用alarm时，之前已为该进程注册的闹钟时间还没有超时，则该闹钟时间的余留值作为本次alarm函数调用的值返回。以前注册的闹钟时间则被新值代替。如果本次调用的seconds值为0，则取消以前的闹钟时间，将其余留值作为本次alarm调用的返回值。</p><p>pause函数使调用进程挂起直至捕捉到一个信号。</p><figure class="highlight c"><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pause</span><span class="params">(<span class="keyword">void</span>)</span></span>;</span><br></pre></td></tr></table></figure><p>只有执行一个信号处理程序并从其返回时，pause才返回。在这种情况下，pause返回-1，errno设置为EINTR。</p><h3 id="10-11-信号集"><a href="#10-11-信号集" class="headerlink" title="10.11 信号集"></a>10.11 信号集</h3><p>我们需要一个能表示多个信号——信号集的数据类型。sigset_t用于表示信号集，且定义了下列5种处理信号集的函数。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;signal.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0；若出错，返回-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">sigemptyset</span><span class="params">(<span class="keyword">sigset_t</span> *<span class="built_in">set</span>)</span></span>;  <span class="comment">// 初始化set，清除所有信号</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">sigfillset</span><span class="params">(<span class="keyword">sigset_t</span> *<span class="built_in">set</span>)</span></span>;   <span class="comment">// 初始化set，使其包含所有信号</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">sigaddset</span><span class="params">(<span class="keyword">sigset_t</span> *<span class="built_in">set</span>, <span class="keyword">int</span> signo)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">sigdelset</span><span class="params">(<span class="keyword">sigset_t</span> *<span class="built_in">set</span>, <span class="keyword">int</span> signo)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若真，返回1；若假，返回0 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">sigismember</span><span class="params">(<span class="keyword">const</span> <span class="keyword">sigset_t</span> *<span class="built_in">set</span>, <span class="keyword">int</span> signo)</span></span>;</span><br></pre></td></tr></table></figure><p>所有应用程序在使用信号集前，要对该信号集调用sigemptyset或sigfillset一次。</p><p>这些函数以及sigset_t实际都是使用位操作实现的，某一位代表一个信号。</p><h3 id="10-12-函数sigprocmask"><a href="#10-12-函数sigprocmask" class="headerlink" title="10.12 函数sigprocmask"></a>10.12 函数sigprocmask</h3><p>一个进程的信号屏蔽字规定了当前阻塞而不能递送给该进程的信号集。调用函数sigprocmask可以检测或更改，或同时进行检测和更改进程的信号屏蔽字。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;signal.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0；若失败，返回-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">sigprocmask</span><span class="params">(<span class="keyword">int</span> how, <span class="keyword">const</span> <span class="keyword">sigset_t</span> *<span class="built_in">set</span>, <span class="keyword">sigset_t</span> *oset)</span></span>;</span><br></pre></td></tr></table></figure><p>how参数指示如何修改当前信号屏蔽字，可选值如下表：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210522090902328.png" alt="image-20210522090902328"></p><p>SIG_BLOCK是或操作，而SIG_SETMASK则是赋值操作。注意，不能阻塞SIGKILL和SIGSTOP信号。</p><p>如果oset是非空指针，那么进程的当前信号屏蔽字通过oset返回。</p><p>若set是一个非空指针，则参数how指示如何修改当前信号屏蔽字。</p><p>如果set是一个空指针，则不改变该进程的信号屏蔽字，how的值也无意义。</p><h3 id="10-13-函数ispending"><a href="#10-13-函数ispending" class="headerlink" title="10.13 函数ispending"></a>10.13 函数ispending</h3><p>sigpending函数返回一信号集，对于调用进程而言，其中的各信号是阻塞不能递送的，因而也一定是当前未决的。该信号集通过set参数返回。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;signal.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0；若出错，返回-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">sigpending</span><span class="params">(<span class="keyword">sigset_t</span>, *<span class="built_in">set</span>)</span></span>;</span><br></pre></td></tr></table></figure><h3 id="10-14-sigaction"><a href="#10-14-sigaction" class="headerlink" title="10.14 sigaction"></a>10.14 sigaction</h3><p>sigaction函数的功能是检查或修改（或检查并修改）与指定信号相关联的处理动作。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;signal.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0；若出错，返回-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">sigaction</span><span class="params">(<span class="keyword">int</span> signo, <span class="keyword">const</span> struct sigaction *act, struct sigaction oact)</span></span>;</span><br></pre></td></tr></table></figure><p>signo是要检测或修改其具体动作的信号编号。若act指针非空，则要修改其动作。如果oact指针非空，则系统经由oact指针返回该信号的上一个动作。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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="class"><span class="keyword">struct</span> <span class="title">sigaction</span> &#123;</span></span><br><span class="line">    <span class="keyword">void</span> (*sa_handler)(<span class="keyword">int</span>);  <span class="comment">/* addr of signal handler */</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">sigset_t</span> sa_mask;         <span class="comment">/* additional signals to block */</span></span><br><span class="line">    <span class="keyword">int</span>      sa_flags;        <span class="comment">/* signal options, Figure 10.16 */</span></span><br><span class="line">    <span class="comment">/* alternate handler */</span></span><br><span class="line">    <span class="keyword">void</span>     (*sa_sigaction)(<span class="keyword">int</span> <span class="keyword">siginfo_t</span> *, <span class="keyword">void</span> *);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="10-15-函数sigsetjmp和函数siglongjmp"><a href="#10-15-函数sigsetjmp和函数siglongjmp" class="headerlink" title="10.15 函数sigsetjmp和函数siglongjmp"></a>10.15 函数sigsetjmp和函数siglongjmp</h3><p>sigsetjmp、siglongjmp与setjmp、longjmp之间的区别在于sigsetjmp多了一个参数savemask，当此参数为非0值使，sigsetjmp在env中保存进程的当前信号屏蔽字。当从setlongjmp跳回时，恢复保存的信号屏蔽字。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;setjmp.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若直接调用，返回0；若从siglongjmp调用返回，则返回非0 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">sigsetjmp</span><span class="params">(sigjmp_buf env, <span class="keyword">int</span> savemask)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">siglongjmp</span><span class="params">(sigjmp_buf env, <span class="keyword">int</span> val)</span></span>;</span><br></pre></td></tr></table></figure><h3 id="10-16-函数sigsuspend"><a href="#10-16-函数sigsuspend" class="headerlink" title="10.16 函数sigsuspend"></a>10.16 函数sigsuspend</h3><p>在一个原子操作中先恢复信号屏蔽字，然后使进程休眠，这种功能使由sigsuspend函数提供的。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;signal.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 返回-1，并将errno设置为EINTR */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">sigsuspend</span><span class="params">(<span class="keyword">const</span> <span class="keyword">sigset_t</span> *sigmask)</span></span>;</span><br></pre></td></tr></table></figure><p>sigsuspend相当于先屏蔽sigmask，接着pause，在接收到一个信号后并处理，pasue返回，然后将屏蔽字恢复到设置sigmask之前。注意，是设置屏蔽字SIG_SETMASK，而不是添加SIG_BLOCK。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210522103549250.png" alt=""></p><p>例子1：保护代码临界区，不被特定信号中断</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;apue.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">sig_int</span><span class="params">(<span class="keyword">int</span>)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">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="keyword">sigset_t</span> newmask, oldmask, waitmask;</span><br><span class="line"></span><br><span class="line">    pr_mask(<span class="string">&quot;program start: &quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (signal(SIGINT, sig_int) == SIG_ERR)</span><br><span class="line">        err_sys(<span class="string">&quot;signal(SIG_INT) error&quot;</span>);</span><br><span class="line"></span><br><span class="line">    sigemptyset(&amp;waitmask);</span><br><span class="line">    sigaddset(&amp;waitmask, SIGUSR1);</span><br><span class="line">    </span><br><span class="line">    sigemptyset(&amp;newmask);</span><br><span class="line">    sigaddset(&amp;newmask, SIGINT);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (sigprocmask(SIG_BLOCK, &amp;newmask, &amp;oldmask) &lt; <span class="number">0</span>)</span><br><span class="line">        err_sys(<span class="string">&quot;SIG_BLOCK error&quot;</span>);</span><br><span class="line"></span><br><span class="line">    pr_mask(<span class="string">&quot;in critical region: &quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 屏蔽SIGUSR1，返回后恢复原来的状态</span></span><br><span class="line">    <span class="keyword">if</span> (sigsuspend(&amp;waitmask) != <span class="number">-1</span>)</span><br><span class="line">        err_sys(<span class="string">&quot;sigsuspend error&quot;</span>);</span><br><span class="line"></span><br><span class="line">    pr_mask(<span class="string">&quot;after return from sigsuspend: &quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (sigprocmask(SIG_SETMASK, &amp;oldmask, <span class="literal">NULL</span>) &lt; <span class="number">0</span>)</span><br><span class="line">        err_sys(<span class="string">&quot;SIG_SETMASK error&quot;</span>);</span><br><span class="line"></span><br><span class="line">    pr_mask(<span class="string">&quot;program exit: &quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">sig_int</span><span class="params">(<span class="keyword">int</span> signo)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    pr_mask(<span class="string">&quot;program exit: &quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210522113611682.png" alt="image-20210522113611682"></p><p>例子2：等待一个信号处理程序设置一个全局变量：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;apue.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">volatile</span> <span class="keyword">sig_atomic_t</span> quitflag;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">sig_int</span><span class="params">(<span class="keyword">int</span> signo)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (signo == SIGINT)</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;\ninterrupt\n&quot;</span>);</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (signo == SIGQUIT)</span><br><span class="line">        quitflag = <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="keyword">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="keyword">sigset_t</span> newmask, oldmask, zeromask;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (signal(SIGINT, sig_int) == SIG_ERR)</span><br><span class="line">        err_sys(<span class="string">&quot;signal (SIGINT) error&quot;</span>);</span><br><span class="line">    <span class="keyword">if</span> (signal(SIGQUIT, sig_int) == SIG_ERR)</span><br><span class="line">        err_sys(<span class="string">&quot;signal (SIGQUIT) error&quot;</span>);</span><br><span class="line"></span><br><span class="line">    sigemptyset(&amp;zeromask);</span><br><span class="line">    sigemptyset(&amp;newmask);</span><br><span class="line">    sigaddset(&amp;newmask, SIGQUIT);</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* </span></span><br><span class="line"><span class="comment">     * block SIGQUIT and save current signal mask.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">if</span> (sigprocmask(SIG_BLOCK, &amp;newmask, &amp;oldmask) &lt; <span class="number">0</span>)</span><br><span class="line">        err_sys(<span class="string">&quot;SIG_BLOCK error&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (quitflag == <span class="number">0</span>)</span><br><span class="line">        sigsuspend(&amp;zeromask);</span><br><span class="line"></span><br><span class="line">    quitflag = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">     * Reset signal mask whick unblocks SIGQUIT</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">if</span> (sigprocmask(SIG_SETMASK, &amp;oldmask, <span class="literal">NULL</span>) &lt; <span class="number">0</span>)</span><br><span class="line">        err_sys(<span class="string">&quot;SIG_SETMASK error&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210522113857786.png" alt="image-20210522113857786"></p><p>例子3：使用信号实现父、子进程之间的同步：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210522113947248.png" alt="image-20210522113947248"></p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210522114002984.png" alt="image-20210522114002984"></p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210522114015840.png" alt="image-20210522114015840"></p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210522114031247.png" alt="image-20210522114031247"></p><h2 id="第11章-线程"><a href="#第11章-线程" class="headerlink" title="第11章 线程"></a>第11章 线程</h2><h3 id="11-1-引言"><a href="#11-1-引言" class="headerlink" title="11.1 引言"></a>11.1 引言</h3><p>本章将进一步深入理解进程，了解如何使用多个控制线程在单进程环境中执行多个任务。一个进程中的所有线程都可以访问该进程的组成部件，如文件描述符和内存。本章的最后将讨论目前可用的同步进制，防止多个线程在共享资源时出现不一致的问题。</p><h3 id="11-2-线程概念"><a href="#11-2-线程概念" class="headerlink" title="11.2 线程概念"></a>11.2 线程概念</h3><p>每个线程都包含有表示执行环境所必须的信息，其中包括进程中标识线程的线程ID、一组寄存器值、栈、调度优先级和策略、信号屏蔽字、errno变量以及线程私有数据。一个进程的所有信息对该进程的所有线程都是共享的，包括可执行代码、程序的全局内存、栈以及文件描述符。</p><h3 id="11-3-线程标识"><a href="#11-3-线程标识" class="headerlink" title="11.3 线程标识"></a>11.3 线程标识</h3><p>每个线程有一个线程ID，线程ID只有在它所属的进程上下文中才有意义。</p><p>线程ID是用pthread_t数据结构来表示的。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若相等，返回非0数值；否则，返回0 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_equal</span><span class="params">(<span class="keyword">pthread_t</span> tid1, <span class="keyword">pthread_t</span> tid2)</span></span>;</span><br></pre></td></tr></table></figure><p>线程可以调用pthread_self函数获得自身的线程ID：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 返回调用线程的线程ID */</span></span><br><span class="line"><span class="function"><span class="keyword">pthread_t</span> <span class="title">pthread_self</span><span class="params">(<span class="keyword">void</span>)</span></span>;</span><br></pre></td></tr></table></figure><h3 id="11-4-线程创建"><a href="#11-4-线程创建" class="headerlink" title="11.4 线程创建"></a>11.4 线程创建</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0；否则，返回错误编号 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_create</span><span class="params">(<span class="keyword">pthread_t</span> *tidp, <span class="keyword">const</span> <span class="keyword">pthread_attr_t</span> *attr, <span class="keyword">void</span> *(*start_rtn)(<span class="keyword">void</span> *), <span class="keyword">void</span> *arg)</span></span>;</span><br></pre></td></tr></table></figure><p>当pthread_create成功返回时，新创建线程的线程ID会被设置成tidp指向的内存单元。attr参数用于定制各种不同的线程属性，为NULL时表示创建一个具有默认属性的线程。新创建的线程从start_rtn函数的地址开始运行，该函数只有一个无指针类型参数arg。如果需要向start_rtn函数传递的参数有一个以上，那么需要把这些参数放到一个结构中，然后把这个结构的地址作为arg参数传入。</p><p>线程运行时并不能保证哪个线程会先运行：是新创建的线程，还是调用线程。新创建的线程可以访问进程的地址空间，并且继承调用线程的浮点环境和信号屏蔽字，但是该线程的挂起信号集会被清除。</p><h3 id="11-5-线程终止"><a href="#11-5-线程终止" class="headerlink" title="11.5 线程终止"></a>11.5 线程终止</h3><p>如果进程中的任意线程调用了exit、_Exit或者_exit，那么整个进程就会终止。</p><p>单个线程可以通过3种方式退出，因此可以在不中止整个进程的情况下，停止它的控制流。</p><ul><li>线程可以简单地从启动例程中返回，返回值是线程地退出码。</li><li>线程可以被同一进程中地其他线程取消。</li><li>线程调用pthread_exit。</li></ul><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">pthread_exit</span><span class="params">(<span class="keyword">void</span> *rval_ptr)</span></span>;</span><br></pre></td></tr></table></figure><p>rval_ptr参数是一个无类型指针，与传给启动例程的单个参数类似。进程中的其他线程也可以通过调用pthread_join函数访问到这个指针。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0；否则，返回错误编号 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_join</span><span class="params">(<span class="keyword">pthread_t</span> thread, <span class="keyword">void</span> **rval_ptr)</span></span>;</span><br></pre></td></tr></table></figure><p>例子1：获取已终止的线程的退出码</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;apue.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> *<span class="title">thr_fn1</span><span class="params">(<span class="keyword">void</span> *arg)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;thread 1 returning\n&quot;</span>);</span><br><span class="line">    <span class="keyword">return</span> ((<span class="keyword">void</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="keyword">void</span> *<span class="title">thr_fn2</span><span class="params">(<span class="keyword">void</span> *arg)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;thread 2 exiting\n&quot;</span>);</span><br><span class="line">    pthread_exit((<span class="keyword">void</span> *)<span class="number">2</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">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="keyword">int</span> err;</span><br><span class="line">    <span class="keyword">pthread_t</span> tid1, tid2;</span><br><span class="line">    <span class="keyword">void</span> *tret;</span><br><span class="line"></span><br><span class="line">    err = pthread_create(&amp;tid1, <span class="literal">NULL</span>, thr_fn1, <span class="literal">NULL</span>);</span><br><span class="line">    <span class="keyword">if</span> (err != <span class="number">0</span>)</span><br><span class="line">        err_exit(err, <span class="string">&quot;can&#x27;t create thread 1&quot;</span>);</span><br><span class="line">    err = pthread_create(&amp;tid2, <span class="literal">NULL</span>, thr_fn2, <span class="literal">NULL</span>);</span><br><span class="line">    <span class="keyword">if</span> (err != <span class="number">0</span>)</span><br><span class="line">        err_exit(err, <span class="string">&quot;can&#x27;t create thread 2&quot;</span>);</span><br><span class="line">    err = pthread_join(tid1, &amp;tret);</span><br><span class="line">    <span class="keyword">if</span> (err != <span class="number">0</span>)</span><br><span class="line">        err_exit(err, <span class="string">&quot;can&#x27;t join with thread 1&quot;</span>);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;thread 1 exit code %ld\n&quot;</span>, (<span class="keyword">long</span>)tret);</span><br><span class="line">    err = pthread_join(tid2, &amp;tret);</span><br><span class="line">    <span class="keyword">if</span> (err != <span class="number">0</span>)</span><br><span class="line">        err_exit(err, <span class="string">&quot;can&#x27;t join with thread 2&quot;</span>);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;thread 2 exit code %ld\n&quot;</span>, (<span class="keyword">long</span>)tret);</span><br><span class="line">    <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210524101213164.png" alt="image-20210524101213164"></p><p>例子2：自动变量作为pthread_exit的参数时出现的问题</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;apue.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">foo</span> &#123;</span></span><br><span class="line">    <span class="keyword">int</span> a, b, c, d;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">printfoo</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *s, <span class="keyword">const</span> struct foo *fp)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;%s&quot;</span>, s);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot; structure at 0x%lx\n&quot;</span>, (<span class="keyword">unsigned</span> <span class="keyword">long</span>)fp);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot; foo.a = %d\n&quot;</span>, fp-&gt;a);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot; foo.b = %d\n&quot;</span>, fp-&gt;b);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot; foo.c = %d\n&quot;</span>, fp-&gt;c);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot; foo.d = %d\n&quot;</span>, fp-&gt;d);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> *<span class="title">thr_fn1</span><span class="params">(<span class="keyword">void</span> *arg)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">foo</span> <span class="title">foo</span> =</span> &#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>&#125;;</span><br><span class="line">    printfoo(<span class="string">&quot;thread 1:\n&quot;</span>, &amp;foo);</span><br><span class="line">    pthread_exit((<span class="keyword">void</span> *)&amp;foo);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> *<span class="title">thr_fn2</span><span class="params">(<span class="keyword">void</span> *arg)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;thread 2: ID is %lu\n&quot;</span>, (<span class="keyword">unsigned</span> <span class="keyword">long</span>)pthread_self());</span><br><span class="line">    pthread_exit((<span class="keyword">void</span> *)<span class="number">0</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">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="keyword">int</span> err;</span><br><span class="line">    <span class="keyword">pthread_t</span> tid1, tid2;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">foo</span> *<span class="title">fp</span>;</span></span><br><span class="line"></span><br><span class="line">    err = pthread_create(&amp;tid1, <span class="literal">NULL</span>, thr_fn1, <span class="literal">NULL</span>);</span><br><span class="line">    <span class="keyword">if</span> (err != <span class="number">0</span>)</span><br><span class="line">        err_exit(err, <span class="string">&quot;can&#x27;t create thread 1&quot;</span>);</span><br><span class="line">    err = pthread_join(tid1, (<span class="keyword">void</span> *)&amp;fp);</span><br><span class="line">    <span class="keyword">if</span> (err != <span class="number">0</span>)</span><br><span class="line">        err_exit(err, <span class="string">&quot;can&#x27;t join with thread 1&quot;</span>);</span><br><span class="line">    sleep(<span class="number">1</span>);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;parent starting second thread\n&quot;</span>);</span><br><span class="line">    err = pthread_create(&amp;tid2, <span class="literal">NULL</span>, thr_fn2, <span class="literal">NULL</span>);</span><br><span class="line">    <span class="keyword">if</span> (err != <span class="number">0</span>)</span><br><span class="line">        err_exit(err, <span class="string">&quot;can&#x27;t create thread 2&quot;</span>);</span><br><span class="line">    sleep(<span class="number">1</span>);</span><br><span class="line">    printfoo(<span class="string">&quot;parent:\n&quot;</span>, fp);</span><br><span class="line">    <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210524101239683.png" alt="image-20210524101239683"></p><p>线程可以通过调用pthread_calcel函数来请求取消同一进程中的其他线程。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0；否则，返回错误编号 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_calcel</span><span class="params">(<span class="keyword">pthread_t</span> tid)</span></span>;</span><br></pre></td></tr></table></figure><p>默认情况下，pthread_calcel函数会使得由tid标识的线程的行为表现为如同调用了参数为PTHREAD_CANCELED的pthread_exit函数，但是，线程可以选择忽略取消或者控制如何被取消。</p><p>线程可以安排它退出时需要调用的函数，这与进程在退出时可以调用atexit函数安排退出是类似的。这样的函数称为线程清理处理程序。一个线程可以建立多个清理处理程序。处理程序记录在栈中，也就是说，它们的执行顺序与它们注册时相反。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">pthread_cleanup_push</span><span class="params">(<span class="keyword">void</span> (*rtn)(<span class="keyword">void</span> *), <span class="keyword">void</span> *arg)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">pthread_cleanup_pop</span><span class="params">(<span class="keyword">int</span> execute)</span></span>;</span><br></pre></td></tr></table></figure><p>当线程执行以下动作时，清理函数rtn是由pthread_cleanup_push函数调度的，调用时只有一个参数arg：</p><ul><li>调用pthread_exit时</li><li>响应取消请求时</li><li>用非零execute参数调用pthread_cleanup_pop时</li></ul><p>如果execute参数设置为0，清理函数将不被调用。不管发生上述哪种情况，pthread_cleanup_pop都将删除上次pthread_cleanup_push调用建立的清理处理程序。</p><p>例子：线程清理程序</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;apue.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">cleanup</span><span class="params">(<span class="keyword">void</span> *arg)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;cleanup: %s\n&quot;</span>, (<span class="keyword">char</span> *)arg);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> *<span class="title">thr_fn1</span><span class="params">(<span class="keyword">void</span> *arg)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;thread 1 start\n&quot;</span>);</span><br><span class="line">    pthread_cleanup_push(cleanup, <span class="string">&quot;thread 1 first handler&quot;</span>);</span><br><span class="line">    pthread_cleanup_push(cleanup, <span class="string">&quot;thread 1 second handler&quot;</span>);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;thread 1 push complete\n&quot;</span>);</span><br><span class="line">    <span class="keyword">if</span> (arg)</span><br><span class="line">        <span class="keyword">return</span> ((<span class="keyword">void</span> *)<span class="number">1</span>);</span><br><span class="line">    pthread_cleanup_pop(<span class="number">0</span>);</span><br><span class="line">    pthread_cleanup_pop(<span class="number">0</span>);</span><br><span class="line">    <span class="keyword">return</span> ((<span class="keyword">void</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="keyword">void</span> *<span class="title">thr_fn2</span><span class="params">(<span class="keyword">void</span> *arg)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;thread 2 start\n&quot;</span>);</span><br><span class="line">    pthread_cleanup_push(cleanup, <span class="string">&quot;thread 2 first handler&quot;</span>);</span><br><span class="line">    pthread_cleanup_push(cleanup, <span class="string">&quot;thread 2 second handler&quot;</span>);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;thread 2 push complete\n&quot;</span>);</span><br><span class="line">    <span class="keyword">if</span> (arg)</span><br><span class="line">        pthread_exit((<span class="keyword">void</span> *)<span class="number">2</span>);</span><br><span class="line">    pthread_cleanup_pop(<span class="number">0</span>);</span><br><span class="line">    pthread_cleanup_pop(<span class="number">0</span>);</span><br><span class="line">    pthread_exit((<span class="keyword">void</span> *)<span class="number">2</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">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="keyword">int</span> err;</span><br><span class="line">    <span class="keyword">pthread_t</span> tid1, tid2;</span><br><span class="line">    <span class="keyword">void</span> *tret;</span><br><span class="line"></span><br><span class="line">    err = pthread_create(&amp;tid1, <span class="literal">NULL</span>, thr_fn1, (<span class="keyword">void</span> *)<span class="number">1</span>);</span><br><span class="line">    <span class="keyword">if</span> (err != <span class="number">0</span>)</span><br><span class="line">        err_exit(err, <span class="string">&quot;can&#x27;t create thread 1&quot;</span>);</span><br><span class="line">    err = pthread_create(&amp;tid2, <span class="literal">NULL</span>, thr_fn2, (<span class="keyword">void</span> *)<span class="number">1</span>);</span><br><span class="line">    <span class="keyword">if</span> (err != <span class="number">0</span>)</span><br><span class="line">        err_exit(err, <span class="string">&quot;can&#x27;t create thread 2&quot;</span>);</span><br><span class="line">    err = pthread_join(tid1, &amp;tret);</span><br><span class="line">    <span class="keyword">if</span> (err != <span class="number">0</span>)</span><br><span class="line">        err_exit(err, <span class="string">&quot;can&#x27;t join with thread 1&quot;</span>);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;thread 1 exit code %ld\n&quot;</span>, (<span class="keyword">long</span>)tret);</span><br><span class="line">    err = pthread_join(tid2, &amp;tret);</span><br><span class="line">    <span class="keyword">if</span> (err != <span class="number">0</span>)</span><br><span class="line">        err_exit(err, <span class="string">&quot;can&#x27;t join with thread 2&quot;</span>);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;thread 2 exit code %ld\n&quot;</span>, (<span class="keyword">long</span>)tret);</span><br><span class="line">    <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210524103810938.png" alt="image-20210524103810938"></p><p>线程函数与进程函数的相似之处：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210524104032515.png" alt="image-20210524104032515"></p><p>默认情况下，线程的终止状态会保存直到对该线程调用ptrhead_join。如果线程已经被分离，线程的底层存储资源可以在线程终止时立即被收回。在线程被分离后，我们不能用pthread_join函数等待它的终止状态，因为对分离状态的线程调用pthread_join会产生未定义行为。可以调用pthread_detach分离线程：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0；否则，返回错误编号 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_detach</span><span class="params">(<span class="keyword">pthread_t</span> tid)</span></span>;</span><br></pre></td></tr></table></figure><h3 id="11-6-线程同步"><a href="#11-6-线程同步" class="headerlink" title="11.6 线程同步"></a>11.6 线程同步</h3><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210524113149181.png" alt="image-20210524113149181"></p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210524113336745.png" alt="image-20210524113336745"></p><h4 id="11-6-1-互斥量"><a href="#11-6-1-互斥量" class="headerlink" title="11.6.1 互斥量"></a>11.6.1 互斥量</h4><p>互斥量（mutex）从本质上来说是一把锁，在访问共享资源前对互斥量进行设置（加锁），在访问完成后释放（解锁）互斥量。在对互斥量进行加锁后，任何其他试图再次对互斥量加锁的线程都会阻塞直到当前线程释放该互斥锁。pthread_mutex_t要初始化为PTHREAD_MUTEX_INITIALIZER（只适用于静态分配的互斥量），或通过pthread_mutex_init函数进行初始化。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0；否则，返回错误编号 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_mutex_init</span><span class="params">(<span class="keyword">pthread_mutex_t</span> *mutex, <span class="keyword">const</span> <span class="keyword">pthread_mutexattr_t</span> *attr)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_mutex_destroy</span><span class="params">(<span class="keyword">pthread_mutex_t</span> *mutex)</span></span>;</span><br></pre></td></tr></table></figure><p>加锁、解锁：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_mutex_lock</span><span class="params">(<span class="keyword">pthread_mutex_t</span> *mutex)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_mutex_trylock</span><span class="params">(<span class="keyword">pthread_mutex_t</span> *mutex)</span></span>;  <span class="comment">// 不阻塞</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_mutex_unlock</span><span class="params">(<span class="keyword">pthread_mutex_t</span> *mutex)</span></span>;</span><br></pre></td></tr></table></figure><p>例子：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">foo</span> &#123;</span></span><br><span class="line">    <span class="keyword">int</span>             f_count;</span><br><span class="line">    <span class="keyword">pthread_mutex_t</span> f_lock;</span><br><span class="line">    <span class="keyword">int</span>             f_id;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function">struct foo *<span class="title">foo_alloc</span><span class="params">(<span class="keyword">int</span> id)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">foo</span> *<span class="title">fp</span>;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> ((fp = <span class="built_in">malloc</span>(<span class="keyword">sizeof</span>(struct foo))) != <span class="literal">NULL</span>) &#123;</span><br><span class="line">        fp-&gt;f_count = <span class="number">1</span>;</span><br><span class="line">        fp-&gt;f_id = id;</span><br><span class="line">        <span class="keyword">if</span> (pthread_mutex_init(&amp;fp-&gt;f_lock, <span class="literal">NULL</span>) != <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="built_in">free</span>(fp);</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">    &#125;</span><br><span class="line">    <span class="keyword">return</span> fp;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">foo_hold</span><span class="params">(struct foo *fp)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    pthread_mutex_lock(&amp;fp-&gt;f_lock);</span><br><span class="line">    fp-&gt;f_count++;</span><br><span class="line">    pthread_mutex_unlock(&amp;fp-&gt;f_lock);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">foo_rele</span><span class="params">(struct foo *fp)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    pthread_mutex_lock(&amp;fp-&gt;f_lock);</span><br><span class="line">    <span class="keyword">if</span> (--fp-&gt;f_count == <span class="number">0</span>) &#123;</span><br><span class="line">        pthread_mutex_unlock(&amp;fp-&gt;f_lock);</span><br><span class="line">        pthread_mutex_destroy(&amp;fp-&gt;f_lock);</span><br><span class="line">        <span class="built_in">free</span>(fp);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        pthread_mutex_unlock(&amp;fp-&gt;f_lock);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="11-6-2-避免死锁"><a href="#11-6-2-避免死锁" class="headerlink" title="11.6.2 避免死锁"></a>11.6.2 避免死锁</h4><p>可以通过仔细控制互斥量的顺序来避免发生死锁。有时候，应用程序的结构使得对互斥量进行排序是很困难的。如果涉及了太多的锁和数据结构，可用的函数并不能把它转换成简单的层次，那么就需要采用另外的方法。这种情况下，可以先释放占用的锁，然后过一段时间再试。如果不能获取锁，可以先释放已经占用的锁，做好清理工作，然后过一段时间再重新试。</p><h4 id="11-6-3-函数pthread-mutex-timedlock"><a href="#11-6-3-函数pthread-mutex-timedlock" class="headerlink" title="11.6.3 函数pthread_mutex_timedlock"></a>11.6.3 函数pthread_mutex_timedlock</h4><p>当线程试图获得一个已加锁的互斥量时，pthread_mutex_timedlock互斥量原语允许绑定线程阻塞时间。pthread_mutex_timedlock函数与pthread_mutex_lock函数基本等价，但是在到达超时时间值时，pthread_mutex_timedlock不会对互斥量进行加锁，而是返回错误码ETIMEDOUT。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pthread.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;time.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0；否则，返回错误编号 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_mutex_timedlock</span><span class="params">(<span class="keyword">pthread_mutex_t</span> *mutex, <span class="keyword">const</span> struct timespec *tsptr)</span></span>;</span><br></pre></td></tr></table></figure><p>例子：用pthread_mutex_timedlock避免永久阻塞</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;apue.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">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="keyword">int</span> err;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">timespec</span> <span class="title">tout</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">tm</span> *<span class="title">tmp</span>;</span></span><br><span class="line">    <span class="keyword">char</span> buf[<span class="number">64</span>];</span><br><span class="line">    <span class="keyword">pthread_mutex_t</span> lock = PTHREAD_MUTEX_INITIALIZER;</span><br><span class="line"></span><br><span class="line">    pthread_mutex_lock(&amp;lock);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;mutex is locked\n&quot;</span>);</span><br><span class="line">    clock_gettime(CLOCK_REALTIME, &amp;tout);</span><br><span class="line">    tmp = localtime(&amp;tout.tv_sec);</span><br><span class="line">    strftime(buf, <span class="keyword">sizeof</span>(buf), <span class="string">&quot;%r&quot;</span>, tmp);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;current time is %s\n&quot;</span>, buf);</span><br><span class="line">    </span><br><span class="line">    tout.tv_sec += <span class="number">10</span>;</span><br><span class="line">    </span><br><span class="line">    err = pthread_mutex_timedlock(&amp;lock, &amp;tout);</span><br><span class="line">    clock_gettime(CLOCK_REALTIME, &amp;tout);</span><br><span class="line">    tmp = localtime(&amp;tout.tv_sec);</span><br><span class="line">    strftime(buf, <span class="keyword">sizeof</span>(buf), <span class="string">&quot;%r&quot;</span>, tmp);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;the time is now %s\n&quot;</span>, buf);</span><br><span class="line">    <span class="keyword">if</span> (err == <span class="number">0</span>)</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;mutex locked again!\n&quot;</span>);</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;can&#x27;t lock mutex again:%s\n&quot;</span>, strerror(err));</span><br><span class="line">    <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210525100414736.png" alt="image-20210525100414736"></p><h4 id="11-6-4-读写锁"><a href="#11-6-4-读写锁" class="headerlink" title="11.6.4 读写锁"></a>11.6.4 读写锁</h4><p>读写锁（reader-writer lock）与互斥量类似，不过读写锁允许更高的并行性。互斥量要么是锁住状态，要么就是不加锁状态，而且一次只有一个线程可以对其加锁。读写锁可以有3种状态：</p><ul><li>读模式下加锁状态</li><li>写模式下加锁状态</li><li>不加锁状态</li></ul><p>一次只有一个线程可以占用写模式的读写锁，但是多个线程可以同时占有读模式的读写锁。</p><p>当读写锁处于写加锁状态时，在这个锁被解锁之前，所有试图对这个锁加锁的线程都会被阻塞。当读写锁处于读加锁状态时，所有试图以读模式对它进行加锁的线程都可以得到访问权，但是任何希望以写模式对此锁进行加锁的线程都会阻塞，直到所有的线程释放它们的读锁为止。</p><p>读写锁非常适合对于数据结构读的次数远大于写的情况。</p><p>读写锁也叫共享互斥锁（shared-exclusive lock）。当读写锁是读模式锁住时，就可以说成是以共享模式锁住的。当它是写模式锁住时，就可以说成是以互斥锁模式锁住的。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0；若出错，返回错误编号 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_rwlock_init</span><span class="params">(<span class="keyword">pthread_rwlock_t</span> *rwlock, <span class="keyword">const</span> <span class="keyword">pthread_rwlockattr_t</span> *attr)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_rwlock_destroy</span><span class="params">(<span class="keyword">pthread_rwlock_t</span> *rwlock)</span></span>;</span><br></pre></td></tr></table></figure><p>除了可以使用pthread_rwlock_init函数对其进行初始化之外，还可对静态变量使用<code>PTHREAD_RWLOCK_INITIALIZER</code>常量进行初始化。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_rwlock_rdlock</span><span class="params">(<span class="keyword">pthread_rwlock_t</span> *rwlock)</span></span>;  <span class="comment">// 加读锁</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_rwlock_wrlock</span><span class="params">(<span class="keyword">pthread_rwlock_t</span> *rwlock)</span></span>;  <span class="comment">// 加写锁</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_rwlock_unlock</span><span class="params">(<span class="keyword">pthread_rwlock_t</span> *rwlock)</span></span>;  <span class="comment">// 解锁</span></span><br></pre></td></tr></table></figure><p>读写锁原语的条件版本：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0；否则，返回错误编号 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_rwlock_tryrdlock</span><span class="params">(<span class="keyword">pthread_rwlock_t</span> *rwlock)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_rwlock_trywrlock</span><span class="params">(<span class="keyword">pthread_rwlock_t</span> *rwlock)</span></span>;</span><br></pre></td></tr></table></figure><p>可以获取锁时，这两个函数返回0。否则，它们返回错误EBUSY。这两个函数可以用于我们前面讨论的遵守某种锁层次但还不能完全避免死锁的情况。</p><p>例子：作业请求队列由单个读写锁保护</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">job</span> &#123;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">job</span> *<span class="title">j_next</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">job</span> *<span class="title">j_prev</span>;</span></span><br><span class="line">    <span class="keyword">pthread_t</span>   j_id;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">queue</span> &#123;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">job</span>      *<span class="title">q_head</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">job</span>      *<span class="title">q_tail</span>;</span></span><br><span class="line">    <span class="keyword">pthread_rwlock_t</span> q_lock;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * Initialize a queue.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">queue_init</span><span class="params">(struct <span class="built_in">queue</span> *qp)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> err;</span><br><span class="line">    </span><br><span class="line">    qp-&gt;q_head = <span class="literal">NULL</span>;</span><br><span class="line">    qp-&gt;q_tail = <span class="literal">NULL</span>;</span><br><span class="line">    err = pthread_rwlock_init(&amp;qp-&gt;q_lock, <span class="literal">NULL</span>);</span><br><span class="line">    <span class="keyword">if</span> (err != <span class="number">0</span>)</span><br><span class="line">        <span class="keyword">return</span> err;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * Insert a job at the head of the queue.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">job_insert</span><span class="params">(struct <span class="built_in">queue</span> *qp, struct job *jp)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    pthread_rwlock_wrlock(&amp;qp-&gt;q_lock);</span><br><span class="line">    jp-&gt;j_next = qp-&gt;q_head;</span><br><span class="line">    jp-&gt;j_prev = <span class="literal">NULL</span>;</span><br><span class="line">    <span class="keyword">if</span> (qp-&gt;q_head != <span class="literal">NULL</span>)</span><br><span class="line">        qp-&gt;q_head-&gt;j_prev = jp;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        qp-&gt;q_tail = jp;</span><br><span class="line">    qp-&gt;q_head = jp;</span><br><span class="line">    pthread_rwlock_unlock(&amp;qp-&gt;q_lock);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * Append a job on the tail of the queue.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">job_append</span><span class="params">(struct <span class="built_in">queue</span> *qp, struct job *jp)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    pthread_rwlock_wrlock(&amp;qp-&gt;qlock);</span><br><span class="line">    jp-&gt;j_next = <span class="literal">NULL</span>;</span><br><span class="line">    jp-&gt;j_prev = qp-&gt;q_tail;</span><br><span class="line">    <span class="keyword">if</span> (qp-&gt;tail != null)</span><br><span class="line">        qp-&gt;tail-&gt;j_next = jp;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        qp-&gt;q_head = jp;</span><br><span class="line">    qp-&gt;q_tail = jp;</span><br><span class="line">    pthread_rwlock_unlock(&amp;qp-&gt;qlock);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * Remove the given job from a queue.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">job_remove</span><span class="params">(struct <span class="built_in">queue</span> *qp, struct job *jp)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    pthread_rwlock_wrlock(&amp;qp-&gt;qlock);</span><br><span class="line">    <span class="keyword">if</span> (jp == qp-&gt;q_head) &#123;</span><br><span class="line">        qp-&gt;q_head = jp-&gt;j_next;</span><br><span class="line">        <span class="keyword">if</span> (qp-&gt;tail == jp)</span><br><span class="line">            qp-&gt;q_tail = <span class="literal">NULL</span>;</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            jp-&gt;j_next-&gt;j_prev = jp-&gt;j_prev;</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (jp == qp-&gt;q_tail) &#123;</span><br><span class="line">        qp-&gt;q_tail = jp-&gt;j_prev;</span><br><span class="line">        jp-&gt;j_prev-&gt;j_next = jp-&gt;j_next;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        jp-&gt;j_prev-&gt;j_next = jp-&gt;j_next;</span><br><span class="line">        jp-&gt;j_next-&gt;j_prev = jp-&gt;j_prev;</span><br><span class="line">    &#125;</span><br><span class="line">    pthread_rwlock_unlock(&amp;qp-&gt;qlock);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * Find a job for the given thread ID.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function">struct job *<span class="title">job_find</span><span class="params">(struct <span class="built_in">queue</span> *qp, <span class="keyword">pthread_t</span> id)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">job</span> *<span class="title">jp</span>;</span></span><br><span class="line">    <span class="keyword">if</span> (pthread_rwlock_rdlock(&amp;qp-&gt;q_lock) != <span class="number">0</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">    <span class="keyword">for</span> (jp = qp-&gt;q_head; jp != <span class="literal">NULL</span>; jp = jp-&gt;j_next)</span><br><span class="line">        <span class="keyword">if</span> (pthread_equal(jp-&gt;j_id, id))</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">    pthread_rwlock_unlock(&amp;qp-&gt;q_lock);</span><br><span class="line">    <span class="keyword">return</span> jp;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="11-6-5-带有超时的读写锁"><a href="#11-6-5-带有超时的读写锁" class="headerlink" title="11.6.5 带有超时的读写锁"></a>11.6.5 带有超时的读写锁</h4><p>与互斥量一样，Single UNIX Specification提供了带有超时的读写锁加锁函数，使应用程序在获取读写锁时避免陷入永久阻塞状态。这两个函数是pthread_rwlock_timedrdlock和pthread_rwlock_timedwrlock。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pthread.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;time.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0；否则，返回错误编号 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_rwlock_timedrdlock</span><span class="params">(<span class="keyword">pthread_rwlock_t</span> *rwlock, <span class="keyword">const</span> struct timespec *tsptr)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_rwlock_timedwrlock</span><span class="params">(<span class="keyword">pthread_rwlock_t</span> *rwlock, <span class="keyword">const</span> struct timespec *tsptr)</span></span>;</span><br></pre></td></tr></table></figure><p>超时返回ETIMEDOUT错误。</p><h4 id="11-6-6-条件变量"><a href="#11-6-6-条件变量" class="headerlink" title="11.6.6 条件变量"></a>11.6.6 条件变量</h4><p>初始化与反初始化：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 成功返回0，出错返回错误编号 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_cond_init</span><span class="params">(<span class="keyword">pthread_cond_t</span> *cond, <span class="keyword">const</span> <span class="keyword">pthread_condattr_t</span> *attr)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_cond_destroy</span><span class="params">(<span class="keyword">pthread_cond_t</span> *cond)</span></span>;</span><br></pre></td></tr></table></figure><p>可使用<code>PTHREAD_COND_INITIALIZER</code>对pthread_cond_t的静态变量进行初始化。</p><p>等待条件变量变为真：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 成功返回0，出错返回错误编号 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_cond_wait</span><span class="params">(<span class="keyword">pthread_cond_t</span> *cond, <span class="keyword">pthread_mutex_t</span> *mutex)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_cond_timedwait</span><span class="params">(<span class="keyword">pthread_cond_t</span> *cond, <span class="keyword">pthread_mutex_t</span> *mutex, <span class="keyword">const</span> struct timespec *tsptr)</span></span>;</span><br></pre></td></tr></table></figure><p>传递给pthread_cond_wait的互斥量对条件进行保护。调用者把锁住的互斥量传给函数，函数然后自动把调用线程放到等待条件的线程列表上，对互斥量解锁。这就关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道，这样线程就不会错过条件的任何变化。pthread_cond_wait返回时，互斥量再次被锁住。</p><p>有两个函数可以用于通知线程条件已经满足。pthread_cond_signal函数至少能唤醒一个等待该条件的线程，而pthread_cond_broadcast函数则能唤醒等待该条件的所有线程。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 成功返回0，出错返回错误编号 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_cond_signal</span><span class="params">(<span class="keyword">pthread_cond_t</span> *cond)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_cond_broadcast</span><span class="params">(<span class="keyword">pthread_cond_t</span> *cond)</span></span>;</span><br></pre></td></tr></table></figure><p>在调用pthread_cond_signal或者pthread_cond_broadcast时，我们说这是在给线程或者条件发信号。必须注意，一定要在改变条件状态以后再给线程发信号。</p><p>例子：结合条件变量和互斥量对线程进行同步</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><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 class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">msg</span> &#123;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">msg</span> *<span class="title">m_next</span>;</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">msg</span> *<span class="title">workq</span>;</span> <span class="comment">// 工作队列</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">pthread_cond_t</span> qready = PTHREAD_COND_INITIALIZER;</span><br><span class="line"><span class="keyword">pthread_mutex_t</span> qlock = PTHREAD_MUTEX_INITIALIZER;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">process_msg</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">msg</span> *<span class="title">mp</span>;</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">        pthread_mutex_lock(&amp;qlock);</span><br><span class="line">        <span class="keyword">while</span> (workq == <span class="literal">NULL</span>)</span><br><span class="line">            pthread_cond_wait(&amp;qready, &amp;qlock);</span><br><span class="line">        mp = workq;</span><br><span class="line">        workq = mp-&gt;m_next;</span><br><span class="line">        pthread_mutex_unlock(&amp;qlock);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</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="function"><span class="keyword">void</span> <span class="title">enqueue_msg</span><span class="params">(struct msg *mp)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    pthread_mutex_lock(&amp;qlock);  <span class="comment">// 把消息放到工作队列时，需要占有互斥量</span></span><br><span class="line">    mp-&gt;next = workq;</span><br><span class="line">    workq = mp;</span><br><span class="line">    pthread_mutex_unlock(&amp;qlock);</span><br><span class="line">    pthread_cond_signal(&amp;qready); <span class="comment">// 在给等待线程发信号时，不需要占有互斥量</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="11-6-7-自旋锁"><a href="#11-6-7-自旋锁" class="headerlink" title="11.6.7 自旋锁"></a>11.6.7 自旋锁</h4><p>自旋锁与互斥量类似，但它不是通过休眠使进程阻塞，而是在获取锁之前已知处于忙等阻塞状态。自旋锁可以用于以下情况：锁被持有的时间短，而且线程并不希望在重新调度上花费太多的成本。</p><p>当线程自旋锁等待锁变为可用时，CPU不能做其他的事情。这也是自旋锁只能够被持有一小段时间的原因。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0；否则，返回错误编号 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_spin_init</span><span class="params">(<span class="keyword">pthread_spinlock_t</span> *lock, <span class="keyword">int</span> pshared)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_spin_destroy</span><span class="params">(<span class="keyword">pthread_spinlock_t</span> *lock)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_spin_lock</span><span class="params">(<span class="keyword">pthread_spinlock_t</span> *lock)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_spin_trylock</span><span class="params">(<span class="keyword">pthread_spinlock_t</span> *lock)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_spin_unlock</span><span class="params">(<span class="keyword">pthread_spinlock_t</span> *lock)</span></span>;</span><br></pre></td></tr></table></figure><h4 id="11-6-8-屏障"><a href="#11-6-8-屏障" class="headerlink" title="11.6.8 屏障"></a>11.6.8 屏障</h4><p>屏障（barrier）是用户协调多个线程并行工作的同步机制。屏障允许每个线程等待，直到所有的合作线程都能到达某一个点，然后从该点继续执行。pthread_join函数就是一种屏障，允许一个线程等待，直到另一个线程退出。</p><p>屏障对象的概念更广，它们允许任意数量的线程等待，直到所有的线程完成处理工作，而线程不需要退出。所有线程达到屏障后可以接着工作。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_barrier_init</span><span class="params">(<span class="keyword">pthread_barrier_t</span> *barrier, <span class="keyword">const</span> <span class="keyword">pthread_barrierattr_t</span> *attr, <span class="keyword">unsigned</span> <span class="keyword">int</span> count)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_barrier_destroy</span><span class="params">(<span class="keyword">pthread_barrier_t</span> *barrier)</span></span>;</span><br></pre></td></tr></table></figure><p>初始化屏障时，可以使用count参数指定，在允许所有线程继续运行之前，必须达到屏障的线程数目。</p><p>可以使用pthread_barrier_wait函数来表明，线程已完成工作，准备等所有其他线程赶上来。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0或者PTHREAD_BARRLER_SERIAL_THREAD；否则，返回错误编号 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pthread_barrier_wait</span><span class="params">(<span class="keyword">pthread_barrier_t</span> *barrier)</span></span>;</span><br></pre></td></tr></table></figure><p>调用pthread_barrier_wait的线程在屏障计数未满足条件时，会进入休眠状态。如果该线程是最后一个调用pthread_barrier_wait的线程，就满足了屏障计数，所有的线程都被唤醒。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;apue.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pthread.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;limits.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/time.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NTHR   8              <span class="comment">// number of threads</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NUMNUM 8000000L       <span class="comment">// number of numbers to sort</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> TNUM   (NUMNUM/NTHR)  <span class="comment">// number of sort per thread</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">long</span> nums[NUMNUM];</span><br><span class="line"><span class="keyword">long</span> snums[NUMNUM];</span><br><span class="line"></span><br><span class="line"><span class="keyword">pthread_barrier_t</span> b;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> SOLARIS</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> heapsort qsort</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">else</span></span></span><br><span class="line"><span class="function"><span class="keyword">extern</span> <span class="keyword">int</span> <span class="title">heapsort</span><span class="params">(<span class="keyword">void</span> *, <span class="keyword">size_t</span>, <span class="keyword">size_t</span>, <span class="keyword">int</span> (*)(<span class="keyword">const</span> <span class="keyword">void</span> *, <span class="keyword">const</span> <span class="keyword">void</span> *))</span></span>;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * Compare two long integers (helper function for heapsort)</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">complong</span><span class="params">(<span class="keyword">const</span> <span class="keyword">void</span> *arg1, <span class="keyword">const</span> <span class="keyword">void</span> *arg2)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">long</span> l1 = *(<span class="keyword">long</span> *)arg1;</span><br><span class="line">    <span class="keyword">long</span> l2 = *(<span class="keyword">long</span> *)arg2;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (l1 == l2)</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (l1 &lt; l2)</span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    <span class="keyword">else</span></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="comment">/*</span></span><br><span class="line"><span class="comment"> * Worker thread to sort a portion of the set of numbers.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> *<span class="title">thr_fn</span><span class="params">(<span class="keyword">void</span> *arg)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">long</span> idx = (<span class="keyword">long</span>)arg;</span><br><span class="line">    </span><br><span class="line">    heapsort(&amp;nums[idx], TNUM, <span class="keyword">sizeof</span>(<span class="keyword">long</span>), complong);</span><br><span class="line">    pthread_barrier_wait(&amp;b);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">     * Go off and perform more work ...</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">return</span> ((<span class="keyword">void</span> *)<span class="number">0</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * Merge the results of the individual sorted ranges.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">merge</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">long</span> idx[NTHR];</span><br><span class="line">    <span class="keyword">long</span> i, minidx, sidx, num;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; NTHR; i++)</span><br><span class="line">        idx[i] = i * TNUM;</span><br><span class="line">    <span class="keyword">for</span> (sidx = <span class="number">0</span>; sidx &lt; NUMNUM; sidx++) &#123;</span><br><span class="line">        num = LONG_MAX;</span><br><span class="line">        <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; NTHR; i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> ((idx[i] &lt; (i + <span class="number">1</span>) * TNUM) &amp;&amp; (nums[idx[i]] &lt; num)) &#123;</span><br><span class="line">                num = nums[idx[i]];</span><br><span class="line">                minidx = i;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        snums[sidx] = nums[idx[minidx]];</span><br><span class="line">        idx[minidx]++;</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="keyword">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="keyword">unsigned</span> <span class="keyword">long</span>   i;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">timeval</span>  <span class="title">start</span>, <span class="title">end</span>;</span></span><br><span class="line">    <span class="keyword">long</span> <span class="keyword">long</span>       startusec, endusec;</span><br><span class="line">    <span class="keyword">double</span>          elapsed;</span><br><span class="line">    <span class="keyword">int</span>             err;</span><br><span class="line">    <span class="keyword">pthread_t</span>       tid;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">     * Create the initial set of numbers to sort.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    srandom(<span class="number">1</span>);</span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; NUMNUM; i++)</span><br><span class="line">        nums[i] = random();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">     * Create 8 threads to sort the numbers.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    gettimeofday(&amp;start, <span class="literal">NULL</span>);</span><br><span class="line">    pthread_barrier_init(&amp;b, <span class="literal">NULL</span>, NTHR + <span class="number">1</span>);</span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; NTHR; i++) &#123;</span><br><span class="line">        err = pthread_create(&amp;tid, <span class="literal">NULL</span>, thr_fn, (<span class="keyword">void</span> *)(i * TNUM));</span><br><span class="line">        <span class="keyword">if</span> (err != <span class="number">0</span>)</span><br><span class="line">            err_exit(err, <span class="string">&quot;can&#x27;t create thread&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    pthread_barrier_wait(&amp;b);</span><br><span class="line">    merge();</span><br><span class="line">    gettimeofday(&amp;end, <span class="literal">NULL</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">     * Print the sorted list.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    startusec = start.tv_sec * <span class="number">1000000</span> + start.tv_usec;</span><br><span class="line">    endusec = end.tv_sec * <span class="number">1000000</span> + end.tv_usec;</span><br><span class="line">    elapsed = (<span class="keyword">double</span>)(endusec - startusec) / <span class="number">1000000.0</span>;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;sort took %.4f seconds\n&quot;</span>, elapsed);</span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; NUMNUM; i++)</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;%ld\n&quot;</span>, snums[i]);</span><br><span class="line">    </span><br><span class="line">    <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="第16章-网络IPC：套接字"><a href="#第16章-网络IPC：套接字" class="headerlink" title="第16章 网络IPC：套接字"></a>第16章 网络IPC：套接字</h2><h3 id="16-1-引言"><a href="#16-1-引言" class="headerlink" title="16.1 引言"></a>16.1 引言</h3><p>本章将描述套接字网络进程间通信接口，进程用该接口能够和其他进程通信，无论是在同一台计算机上还是在不同的计算机上。</p><h3 id="16-2-套接字描述符"><a href="#16-2-套接字描述符" class="headerlink" title="16.2 套接字描述符"></a>16.2 套接字描述符</h3><p>应用程序用套接字描述符访问套接字，套接字描述符被当作一种文件描述符，许多处理文件描述符的函数可以用于处理套接字描述符。</p><p>为创建一个套接字，调用socket函数：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/socket.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回文件描述符；若出错，返回-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">socket</span><span class="params">(<span class="keyword">int</span> domain, <span class="keyword">int</span> type, <span class="keyword">int</span> protocol)</span></span>;</span><br></pre></td></tr></table></figure><p>domain参数确定通信的特征，包括地址格式：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210526095142694.png" alt="image-20210526095142694"></p><p>type参数确定套接字的类型：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210526095219967.png" alt="image-20210526095219967"></p><p>参数protocol通常为0，标识为给定的域和套接字类型选择默认协议，如domain=AF_INET且type=SOCK_STREAM时默认协议是TCP协议，domain=AF_INET且type=SOCK_DGRAM时默认协议是UDP协议。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210526095446277.png" alt="image-20210526095446277"></p><p>虽然套接字描述符本质上是一个文件描述符，但不是所有参数为文件描述符的函数都可接受套接字描述符，如lseek不能以套接字为描述符参数，因为参数不支持文件偏移量的概念。</p><p>调节字通信是双向的。可以采用shutdown函数来禁止一个套接字的I/O：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/socket.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0；若出错，返回-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">shutdown</span><span class="params">(<span class="keyword">int</span> sockfd, <span class="keyword">int</span> how)</span></span>;</span><br></pre></td></tr></table></figure><p>how参数：</p><ul><li>SHUT_RD：关闭读端</li><li>SHUT_WR：关闭写端</li><li>SHUT_RDWR：关闭读写端</li></ul><h3 id="16-3-寻址"><a href="#16-3-寻址" class="headerlink" title="16.3 寻址"></a>16.3 寻址</h3><p>如何标识一个目标通信进程？IP地址+端口号。</p><h4 id="16-3-1-字节序"><a href="#16-3-1-字节序" class="headerlink" title="16.3.1 字节序"></a>16.3.1 字节序</h4><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210526101854646.png" alt="image-20210526101854646"></p><p>字节序是一个处理器架构特性，用于指示数据类型内部的字节如何排序。</p><ul><li>大端字节序：最大字节地址出现在最低有效字节上</li><li>小端字节序：最低有效字节包含最小字节地址</li></ul><p>注意，不管字节如何排序，最高有效字节（MSB）总是在左边，最低有效字节（LSB）总是在右边。如0x04030201，无论字节序如何，最高有效字节都将包含4，最低有效字节都将包含1。接下来想将一个字符指针（cp）强制转换到这个整数地址，就会看到字节序的不同。小端字节序，cp[0]将包含1，大端字节序，cp[0]将包含4。</p><p>网络协议执行了字节序，因此异构计算机系统能够交换协议信息而不会被字节序所混淆。TCP/IP协议栈使用大端字节序。应用程序交换格式化数据时，字节序问题就会出现。对于TCP/IP地址用网络字节序来表示，所以应用程序有时需要在处理器的字节序与网络字节序之间转换他们。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;arpa/inet.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">uint32_t</span> <span class="title">htonl</span><span class="params">(<span class="keyword">uint32_t</span> hostint32)</span></span>;  <span class="comment">/* 以网络字节序表示的32位整数 */</span></span><br><span class="line"><span class="function"><span class="keyword">uint16_t</span> <span class="title">htons</span><span class="params">(<span class="keyword">uint16_t</span> hostint16)</span></span>;  <span class="comment">/* 以网络字节序表示的16位整数 */</span></span><br><span class="line"><span class="function"><span class="keyword">uint32_t</span> <span class="title">ntohl</span><span class="params">(<span class="keyword">uint32_t</span> netint32)</span></span>;   <span class="comment">/* 以主机字节序表示的32位整数 */</span></span><br><span class="line"><span class="function"><span class="keyword">uint16_t</span> <span class="title">ntohs</span><span class="params">(<span class="keyword">uint16_t</span> netint16)</span></span>;   <span class="comment">/* 以主机字节序表示的16位整数 */</span></span><br></pre></td></tr></table></figure><h4 id="16-3-2-地址格式"><a href="#16-3-2-地址格式" class="headerlink" title="16.3.2 地址格式"></a>16.3.2 地址格式</h4><p>一个地址标识一个特定通信域的套接字端点，地址格式与这个特定的通信域相关。为使不同格式地址能够传入到套接字函数，地址会被强制转换成一个通用的地址结构sockaddr：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/socket.h&gt;</span> </span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* In Linux */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">sockaddr</span> &#123;</span></span><br><span class="line">    <span class="keyword">sa_family_t</span>     sa_family;  <span class="comment">/* address family, AF_xxxx */</span></span><br><span class="line">    <span class="keyword">char</span>            sa_data[<span class="number">14</span>];  <span class="comment">/* variable-length address */</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>IPv4的地址使用sockaddr_in表示，IPv6的地址使用sockaddr_in6表示：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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="meta-keyword">include</span> <span class="meta-string">&lt;netinet/in.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">in_addr</span> &#123;</span></span><br><span class="line">    <span class="keyword">in_addr_t</span>       s_addr;  <span class="comment">/* IPv4 address */</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">in6_addr</span> &#123;</span></span><br><span class="line">    <span class="keyword">uint8_t</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// struct sockaddr_in &#123;</span></span><br><span class="line"><span class="comment">//   sa_family_t     sin_family;  /* address family */</span></span><br><span class="line"><span class="comment">//     in_port_t       sin_port;    /* port number */</span></span><br><span class="line"><span class="comment">//     struct in_addr  sin_addr;    /* IPv4 address */</span></span><br><span class="line"><span class="comment">// &#125;;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* In Linux */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">sockaddr_in</span> &#123;</span></span><br><span class="line">  <span class="keyword">sa_family_t</span>      sin_family;  <span class="comment">/* address family */</span></span><br><span class="line">    <span class="keyword">in_port_t</span>        sin_port;    <span class="comment">/* port number */</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">in_addr</span>   <span class="title">sin_addr</span>;</span>    <span class="comment">/* IPv4 address */</span></span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">char</span>    sin_zero[<span class="number">8</span>]; <span class="comment">/* filler */</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>in_addr_t定义成uint32_t，in_port_t定义成uint16_t。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210526105347623.png" alt="image-20210526105347623"></p><p>有时，需要打印出能被人理解而不是计算机所理解的地址格式。BSD网络软件包含函数inet_addr和inet_ntoa，用于二进制地址格式与点分十进制字符表示之间的互相转换，但这些函数仅适用于IPv4地址。有两个新函数inet_ntop和inet_pton具有相似的功能，而且同时支持IPv4地址和IPv6地址。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;arpa/inet.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回地址字符串指针；若出错，返回NULL */</span></span><br><span class="line"><span class="function"><span class="keyword">const</span> <span class="keyword">char</span> *<span class="title">inet_ntop</span><span class="params">(<span class="keyword">int</span> domain, <span class="keyword">const</span> <span class="keyword">void</span> *addr, <span class="keyword">char</span> *str, <span class="keyword">socklen_t</span> size)</span></span>;</span><br><span class="line"><span class="comment">/* 若成功，返回1；若格式无效，返回0；若出错，返回-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">inet_pton</span><span class="params">(<span class="keyword">int</span> domain, <span class="keyword">const</span> <span class="keyword">char</span> str, <span class="keyword">void</span> *addr)</span></span>;</span><br></pre></td></tr></table></figure><p>inet_ntop将网络字节序的二进制地址转换成文本字符串格式。inet_pton将文本字符串格式转换成网络字节序的二进制。</p><h4 id="16-3-3-地址查询"><a href="#16-3-3-地址查询" class="headerlink" title="16.3.3 地址查询"></a>16.3.3 地址查询</h4><p>getaddrinfo函数允许将一个主机名和一个服务名映射到一个地址：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/socket.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;netdb.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0；若出错，返回非0错误码 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">getaddrinfo</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *host, <span class="keyword">const</span> <span class="keyword">char</span> *service, <span class="keyword">const</span> struct addrinfo *hint, struct addrinfo **res)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">freeaddrinfo</span><span class="params">(struct addrinfo *ai)</span></span>;</span><br></pre></td></tr></table></figure><p>需要提供主机名、服务名，或者两者都提供。如果仅仅提供一个名字，另外一个必须是一个空指针。主机名可以是一个节点名或点分格式的主机地址。getaddrinfo函数返回一个链表结构addrinfo，可用freeaddrinfo来释放一个或多个这种结构，这取决于ai_next字段链接起来的结构有多少。</p><p>addrinfo结构定义：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210526111248667.png" alt="image-20210526111248667"></p><p>hint是一个用于过滤的模板，包含ai_family、ai_flags、ai_protocal和ai_socktype字段。剩余的整数字段必须设置为0，指针字段必须为空。ai_flags:</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210526111729974.png" alt="image-20210526111729974"></p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210526112044316.png" alt="image-20210526112044316"></p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210526112143525.png" alt="image-20210526112143525"></p><p>实例：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;apue.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">if</span> defined(SOLARIS)</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;netinet/in.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;netdb.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;arpa/inet.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">if</span> defined(BSD)</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/socket.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;netinet/in.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">print_family</span><span class="params">(struct addrinfo *aip)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot; family &quot;</span>);</span><br><span class="line">    <span class="keyword">switch</span>(aip-&gt;ai_family) &#123;</span><br><span class="line">        <span class="keyword">case</span> AF_INET:</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot;inet&quot;</span>);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">case</span> AF_INET6:</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot;inet6&quot;</span>);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">case</span> AF_UNIX:</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot;unix&quot;</span>);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">case</span> AF_UNSPEC:</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot;unspecified&quot;</span>);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">default</span>:</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot;unknown&quot;</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="function"><span class="keyword">void</span> <span class="title">print_type</span><span class="params">(struct addrinfo *aip)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot; type &quot;</span>);</span><br><span class="line">    <span class="keyword">switch</span>(aip-&gt;ai_socktype) &#123;</span><br><span class="line">        <span class="keyword">case</span> SOCK_STREAM:</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot;stream&quot;</span>);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">case</span> SOCK_DGRAM:</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot;datagram&quot;</span>);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">case</span> SOCK_SEQPACKET:</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot;seqpacket&quot;</span>);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">case</span> SOCK_RAW:</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot;raw&quot;</span>);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">default</span>:</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot;unknown (%d)&quot;</span>, aip-&gt;ai_socktype);</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="keyword">void</span> <span class="title">print_protocol</span><span class="params">(struct addrinfo *aip)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot; protocol &quot;</span>);</span><br><span class="line">    <span class="keyword">switch</span>(aip-&gt;ai_protocol) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="number">0</span>:</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;default&quot;</span>);</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> IPPROTO_TCP:</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;TCP&quot;</span>);</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> IPPROTO_UDP:</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;UDP&quot;</span>);</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> IPPROTO_RAW:</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;raw&quot;</span>);</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">default</span>:</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;unknown (%d)&quot;</span>, aip-&gt;ai_protocol);</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="keyword">void</span> <span class="title">print_flags</span><span class="params">(struct addrinfo *aip)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;flags&quot;</span>);</span><br><span class="line">    <span class="keyword">if</span> (aip-&gt;ai_flags == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot; 0&quot;</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (aip-&gt;ai_flags &amp; AI_PASSIVE)</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot; passive&quot;</span>);</span><br><span class="line">        <span class="keyword">if</span> (aip-&gt;ai_flags &amp; AI_CANONNAME)</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot; canon&quot;</span>);</span><br><span class="line">        <span class="keyword">if</span> (aip-&gt;ai_flags &amp; AI_NUMERICHOST)</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot; numhost&quot;</span>);</span><br><span class="line">        <span class="keyword">if</span> (aip-&gt;ai_flags &amp; AI_NUMERICSERV)</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot; numserv&quot;</span>);</span><br><span class="line">        <span class="keyword">if</span> (aip-&gt;ai_flags &amp; AI_V4MAPPED)</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot; v4mapped&quot;</span>);</span><br><span class="line">        <span class="keyword">if</span> (aip-&gt;ai_flags &amp; AI_ALL)</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot; all&quot;</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="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">char</span> *argv[])</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">addrinfo</span>    *<span class="title">ailist</span>, *<span class="title">aip</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">addrinfo</span>     <span class="title">hint</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">sockaddr_in</span> *<span class="title">sinp</span>;</span></span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">char</span>         *addr;</span><br><span class="line">    <span class="keyword">int</span>                 err;</span><br><span class="line">    <span class="keyword">char</span>                abuf[INET_ADDRSTRLEN];</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (argc != <span class="number">3</span>)</span><br><span class="line">        err_quit(<span class="string">&quot;usage: %s nodename service&quot;</span>, argv[<span class="number">0</span>]);</span><br><span class="line">    hint.ai_flags = AI_CANONNAME;</span><br><span class="line">    hint.ai_family = <span class="number">0</span>;</span><br><span class="line">    hint.ai_socktype = <span class="number">0</span>;</span><br><span class="line">    hint.ai_protocol = <span class="number">0</span>;</span><br><span class="line">    hint.ai_addrlen = <span class="number">0</span>;</span><br><span class="line">    hint.ai_canonname = <span class="literal">NULL</span>;</span><br><span class="line">    hint.ai_addr = <span class="literal">NULL</span>;</span><br><span class="line">    hint.ai_next = <span class="literal">NULL</span>;</span><br><span class="line">    <span class="keyword">if</span> ((err = getaddrinfo(argv[<span class="number">1</span>], argv[<span class="number">2</span>], &amp;hint, &amp;ailist)) != <span class="number">0</span>)</span><br><span class="line">        err_quit(<span class="string">&quot;getaddrinfo error: %s&quot;</span>, gai_strerror(err));</span><br><span class="line">    <span class="keyword">for</span> (aip = ailist; aip != <span class="literal">NULL</span>; aip = aip-&gt;ai_next) &#123;</span><br><span class="line">        print_flags(aip);</span><br><span class="line">        print_family(aip);</span><br><span class="line">        print_type(aip);</span><br><span class="line">        print_protocol(aip);</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;\n\thost %s&quot;</span>, aip-&gt;ai_canonname?aip-&gt;ai_canonname:<span class="string">&quot;-&quot;</span>);</span><br><span class="line">        <span class="keyword">if</span> (aip-&gt;ai_family == AF_INET) &#123;</span><br><span class="line">            sinp = (struct sockaddr_in *)aip-&gt;ai_addr;</span><br><span class="line">            addr = inet_ntop(AF_INET, &amp;sinp-&gt;sin_addr, abuf, INET_ADDRSTRLEN);</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot; address %s&quot;</span>, addr?addr:<span class="string">&quot;unknown&quot;</span>);</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot; port %d&quot;</span>, ntohs(sinp-&gt;sin_port));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;\n&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210526115701669.png" alt="image-20210526115701669"></p><h4 id="16-3-4-将套接字与地址关联"><a href="#16-3-4-将套接字与地址关联" class="headerlink" title="16.3.4 将套接字与地址关联"></a>16.3.4 将套接字与地址关联</h4><p>使用bind函数来关联地址和套接字：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/socket.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0；若出错，返回-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">bind</span><span class="params">(<span class="keyword">int</span> sockfd, <span class="keyword">const</span> struct sockaddr *addr, <span class="keyword">socklen_t</span> len)</span></span>;</span><br></pre></td></tr></table></figure><p>可以调用getsockname函数来发现绑定到套接字上的地址：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/socket.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0；若出错，返回-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">getsockname</span><span class="params">(<span class="keyword">int</span> sockfd, struct sockaddr *addr, <span class="keyword">socklen_t</span> *alenp)</span></span>;</span><br></pre></td></tr></table></figure><p>如果套接字已经和对等方连接，可以调用getpeername函数来找到对方的地址：</p><figure class="highlight c"><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/socket.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">getpeername</span><span class="params">(<span class="keyword">int</span> sockfd, struct sockaddr *addr, <span class="keyword">socklen_t</span> *alenp)</span></span>;</span><br></pre></td></tr></table></figure><h3 id="16-4-建立连接"><a href="#16-4-建立连接" class="headerlink" title="16.4 建立连接"></a>16.4 建立连接</h3><p>如果要处理一个面向连接的网络服务（SOCK_STREAM或SOCK_SEQPACKET），那么在开始交换数据以前，需要在请求服务的进程套接字（客户端）和提供服务的进程套接字（服务器）之间建立一个连接。使用connect函数建立连接：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/socket.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0；若出错，返回-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">connect</span><span class="params">(<span class="keyword">int</span> sockfd, <span class="keyword">const</span> struct sockaddr *addr, <span class="keyword">socklen_t</span> len)</span></span>;</span><br></pre></td></tr></table></figure><p>addr是我们想与之通信的服务器地址。如果sockfd没有绑定到一个地址，connect会给调用者绑定一个默认地址。</p><p>服务器调用listen函数来宣告它愿意接受连接请求：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/socket.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回0；若出错，返回-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">listen</span><span class="params">(<span class="keyword">int</span> sockfd, <span class="keyword">int</span> backlog)</span></span>;</span><br></pre></td></tr></table></figure><p>一旦服务器调用了listen，所用的套接字就能接受连接请求。使用accept函数获得连接请求并建立连接：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/socket.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 若成功，返回套接字描述符；若出错，返回-1 */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">accept</span><span class="params">(<span class="keyword">int</span> sockfd, struct sockaddr *addr, <span class="keyword">socklen_t</span> *len)</span></span>;</span><br></pre></td></tr></table></figure><h3 id="16-5-数据传输"><a href="#16-5-数据传输" class="headerlink" title="16.5 数据传输"></a>16.5 数据传输</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/socket.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">ssize_t</span> <span class="title">send</span><span class="params">(<span class="keyword">int</span> sockfd, <span class="keyword">const</span> <span class="keyword">void</span> *buf, <span class="keyword">size_t</span> nbytes, <span class="keyword">int</span> flags)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">ssize_t</span> <span class="title">sendto</span><span class="params">(<span class="keyword">int</span> sockfd, <span class="keyword">const</span> <span class="keyword">void</span> *buf, <span class="keyword">size_t</span> nbytes, <span class="keyword">int</span> flags, <span class="keyword">const</span> struct sockaddr *destaddr, <span class="keyword">socklen_t</span> destlen)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">ssize_t</span> <span class="title">sendmsg</span><span class="params">(<span class="keyword">int</span> sockfd, <span class="keyword">const</span> struct msghdr *msg, <span class="keyword">int</span> flags)</span></span>;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>flags:</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210526132617915.png" alt="image-20210526132617915"></p><p>sendto可以在无连接的套接字上指定一个目标地址，sendmsg使用msghdr结构指定多重缓冲区。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210526133122919.png" alt="image-20210526133122919"></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/socket.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">ssize_t</span> <span class="title">recv</span><span class="params">(<span class="keyword">int</span> sockfd, <span class="keyword">void</span> *buf, <span class="keyword">size_t</span> nbytes, <span class="keyword">int</span> flags)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">ssize_t</span> <span class="title">recvfrom</span><span class="params">(<span class="keyword">int</span> sockfd, <span class="keyword">void</span> *buf, <span class="keyword">size_t</span> len, <span class="keyword">int</span> flags, struct sockaddr *addr, <span class="keyword">socklen_t</span> *addrlen)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">ssize_t</span> <span class="title">recvmsg</span><span class="params">(<span class="keyword">int</span> sockfd, struct msghdr *msg, <span class="keyword">int</span> flags)</span></span>;</span><br></pre></td></tr></table></figure><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210526133446600.png" alt="image-20210526133446600"></p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210526133457811.png" alt="image-20210526133457811"></p><p>实例：面向连接的ruptime</p><p>客户端：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;apue.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;netdb.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;errno.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/socket.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> BUFLEN   128</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> MAXSLEEP 128</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">connect_retry</span><span class="params">(<span class="keyword">int</span> domain, <span class="keyword">int</span> type, <span class="keyword">int</span> protocol, <span class="keyword">const</span> struct sockaddr *addr, <span class="keyword">socklen_t</span> alen)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> numsec, fd;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (numsec = <span class="number">1</span>; numsec &lt;= MAXSLEEP; numsec &lt;&lt;= <span class="number">1</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> ((fd = socket(domain, type, protocol)) &lt; <span class="number">0</span>)</span><br><span class="line">            <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">        <span class="keyword">if</span> (connect(fd, addr, alen) == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> fd;</span><br><span class="line">        &#125;</span><br><span class="line">        close(fd);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (numsec &lt;= MAXSLEEP / <span class="number">2</span>)</span><br><span class="line">            sleep(numsec);</span><br><span class="line">    &#125;</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="keyword">void</span> <span class="title">print_uptime</span><span class="params">(<span class="keyword">int</span> sockfd)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> n;</span><br><span class="line">    <span class="keyword">char</span> buf[BUFLEN];</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> ((n = recv(sockfd, buf, BUFLEN, <span class="number">0</span>)) &gt; <span class="number">0</span>)</span><br><span class="line">        write(STDOUT_FILENO, buf, n);</span><br><span class="line">    <span class="keyword">if</span> (n &lt; <span class="number">0</span>)</span><br><span class="line">        err_sys(<span class="string">&quot;recv error&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">char</span> *argv[])</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">addrinfo</span> *<span class="title">ailist</span>, *<span class="title">aip</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">addrinfo</span>  <span class="title">hint</span>;</span></span><br><span class="line">    <span class="keyword">int</span>              sockfd, err;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (argc != <span class="number">2</span>)</span><br><span class="line">        err_quit(<span class="string">&quot;usage: ruptime hostname&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="built_in">memset</span>(&amp;hint, <span class="number">0</span>, <span class="keyword">sizeof</span>(hint));</span><br><span class="line">    hint.ai_socktype = SOCK_STREAM;</span><br><span class="line">    hint.ai_canonname = <span class="literal">NULL</span>;</span><br><span class="line">    hint.ai_addr = <span class="literal">NULL</span>;</span><br><span class="line">    hint.ai_next = <span class="literal">NULL</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> ((err = getaddrinfo(argv[<span class="number">1</span>], <span class="string">&quot;ruptime&quot;</span>, &amp;hint, &amp;ailist)) != <span class="number">0</span>)</span><br><span class="line">        err_quit(<span class="string">&quot;getaddrinfo error: %s&quot;</span>, gai_strerror(err));</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (aip = ailist; aip != <span class="literal">NULL</span>; aip = aip-&gt;ai_next) &#123;</span><br><span class="line">        <span class="keyword">if</span> ((sockfd = connect_retry(aip-&gt;ai_family, SOCK_STREAM, <span class="number">0</span>, aip-&gt;ai_addr, aip-&gt;ai_addrlen)) &lt; <span class="number">0</span>) &#123;</span><br><span class="line">            err = errno;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            print_uptime(sockfd);</span><br><span class="line">            <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    err_exit(err, <span class="string">&quot;can&#x27;t connect to %s&quot;</span>, argv[<span class="number">1</span>]);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>服务器1：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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="comment">/*************************************************************************</span></span><br><span class="line"><span class="comment">&gt; File Name: ../server1.c</span></span><br><span class="line"><span class="comment">&gt; Author: </span></span><br><span class="line"><span class="comment">&gt; Mail: </span></span><br><span class="line"><span class="comment">&gt; Created Time: Wed 26 May 2021 01:49:05 PM CST</span></span><br><span class="line"><span class="comment"> ************************************************************************/</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;apue.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;netdb.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;errno.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;syslog.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/socket.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> BUFLEN 128</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> QLEN 10</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifndef</span> HOST_NAME_MAX</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> HOST_NAME_MAX 256</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">initserver</span><span class="params">(<span class="keyword">int</span> type, <span class="keyword">const</span> struct sockaddr *addr, <span class="keyword">socklen_t</span> alen, <span class="keyword">int</span> qlen)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> fd;</span><br><span class="line">    <span class="keyword">int</span> err = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> ((fd = socket(addr-&gt;sa_family, type, <span class="number">0</span>)) &lt; <span class="number">0</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    <span class="keyword">if</span> (bind(fd, addr, alen) &lt; <span class="number">0</span>)</span><br><span class="line">        <span class="keyword">goto</span> errout;</span><br><span class="line">    <span class="keyword">if</span> (type == SOCK_STREAM || type == SOCK_SEQPACKET)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (listen(fd, qlen) &lt; <span class="number">0</span>)</span><br><span class="line">            <span class="keyword">goto</span> errout;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> fd;</span><br><span class="line"></span><br><span class="line">errout:</span><br><span class="line">    err = errno;</span><br><span class="line">    close(fd);</span><br><span class="line">    errno = err;</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="keyword">void</span> <span class="title">server</span><span class="params">(<span class="keyword">int</span> sockfd)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> clfd;</span><br><span class="line">    FILE *fp;</span><br><span class="line">    <span class="keyword">char</span> buf[BUFLEN];</span><br><span class="line"></span><br><span class="line">    set_cloexec(sockfd);</span><br><span class="line">    <span class="keyword">for</span> (;;)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> ((clfd = accept(sockfd, <span class="literal">NULL</span>, <span class="literal">NULL</span>)) &lt; <span class="number">0</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            syslog(LOG_ERR, <span class="string">&quot;ruptimed: accept error: %s&quot;</span>, strerror(errno));</span><br><span class="line">            <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        set_cloexec(clfd);</span><br><span class="line">        <span class="keyword">if</span> ((fp = popen(<span class="string">&quot;/usr/bin/uptime&quot;</span>, <span class="string">&quot;r&quot;</span>)) == <span class="literal">NULL</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="built_in">sprintf</span>(buf, <span class="string">&quot;error: %s\n&quot;</span>, strerror(errno));</span><br><span class="line">            send(clfd, buf, <span class="built_in">strlen</span>(buf), <span class="number">0</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">while</span> (fgets(buf, BUFLEN, fp) != <span class="literal">NULL</span>)</span><br><span class="line">                send(clfd, buf, <span class="built_in">strlen</span>(buf), <span class="number">0</span>);</span><br><span class="line">            pclose(fp);</span><br><span class="line">        &#125;</span><br><span class="line">        close(clfd);</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="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">char</span> *argv[])</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">addrinfo</span> *<span class="title">ailist</span>, *<span class="title">aip</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">addrinfo</span> <span class="title">hint</span>;</span></span><br><span class="line">    <span class="keyword">int</span> sockfd, err, n;</span><br><span class="line">    <span class="keyword">char</span> *host;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (argc != <span class="number">1</span>)</span><br><span class="line">        err_quit(<span class="string">&quot;usage: ruptimed&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> ((n = sysconf(_SC_HOST_NAME_MAX)) &lt; <span class="number">0</span>)</span><br><span class="line">        n = HOST_NAME_MAX;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* malloc space for host to save hostname or address */</span></span><br><span class="line">    <span class="keyword">if</span> ((host = <span class="built_in">malloc</span>(n)) == <span class="literal">NULL</span>)</span><br><span class="line">        err_sys(<span class="string">&quot;malloc error&quot;</span>);</span><br><span class="line"></span><br><span class="line">    daemonize(<span class="string">&quot;ruptimed&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="built_in">memset</span>(&amp;hint, <span class="number">0</span>, <span class="keyword">sizeof</span>(hint));</span><br><span class="line">    hint.ai_flags = AI_CANONNAME;</span><br><span class="line">    hint.ai_socktype = SOCK_STREAM;</span><br><span class="line">    hint.ai_canonname = <span class="literal">NULL</span>;</span><br><span class="line">    hint.ai_addr = <span class="literal">NULL</span>;</span><br><span class="line">    hint.ai_next = <span class="literal">NULL</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> ((err = getaddrinfo(host, <span class="string">&quot;ruptime&quot;</span>, &amp;hint, &amp;ailist)) != <span class="number">0</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        syslog(LOG_ERR, <span class="string">&quot;ruptimed: getaddrinfo error: %s&quot;</span>, gai_strerror(err));</span><br><span class="line">        <span class="built_in">exit</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">for</span> (aip = ailist; aip != <span class="literal">NULL</span>; aip = aip-&gt;ai_next)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> ((sockfd = initserver(SOCK_STREAM, aip-&gt;ai_addr, aip-&gt;ai_addrlen, QLEN)) &gt;= <span class="number">0</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            server(sockfd);</span><br><span class="line">            <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>服务器的第二种方式：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;apue.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;netdb.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;errno.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;syslog.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;fcntl.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/socket.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/wait.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> QLEN 10</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifndef</span> HOST_NAME_MAX</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> HOST_NAME_MAX 256</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">initserver</span><span class="params">(<span class="keyword">int</span>, <span class="keyword">const</span> struct sockaddr *, <span class="keyword">socklen_t</span>, <span class="keyword">int</span>)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">serve</span><span class="params">(<span class="keyword">int</span> sockfd)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">char</span> *argv[])</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">addrinfo</span> *<span class="title">ailist</span>, *<span class="title">aip</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">addrinfo</span> <span class="title">hint</span>;</span></span><br><span class="line">    <span class="keyword">int</span> sockfd, err, n;</span><br><span class="line">    <span class="keyword">char</span> *host;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (argc != <span class="number">1</span>)</span><br><span class="line">        err_quit(<span class="string">&quot;usage: ruptimed&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> ((n = sysconf(_SC_HOST_NAME_MAX)) &lt; <span class="number">0</span>)</span><br><span class="line">        n = HOST_NAME_MAX;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* malloc space for host to save hostname or address */</span></span><br><span class="line">    <span class="keyword">if</span> ((host = <span class="built_in">malloc</span>(n)) == <span class="literal">NULL</span>)</span><br><span class="line">        err_sys(<span class="string">&quot;malloc error&quot;</span>);</span><br><span class="line"></span><br><span class="line">    daemonize(<span class="string">&quot;ruptimed&quot;</span>);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;\033[32mDEBUG\033[0m ==&gt; daemonize(\&quot;ruptimed\&quot;) successfully.\n&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="built_in">memset</span>(&amp;hint, <span class="number">0</span>, <span class="keyword">sizeof</span>(hint));</span><br><span class="line">    hint.ai_flags = AI_CANONNAME;</span><br><span class="line">    hint.ai_socktype = SOCK_STREAM;</span><br><span class="line">    hint.ai_canonname = <span class="literal">NULL</span>;</span><br><span class="line">    hint.ai_addr = <span class="literal">NULL</span>;</span><br><span class="line">    hint.ai_next = <span class="literal">NULL</span>;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;\033[32mDEBUG\033[0m ==&gt; initialize hint successfully\n&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> ((err = getaddrinfo(host, <span class="string">&quot;ruptime&quot;</span>, &amp;hint, &amp;ailist)) != <span class="number">0</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        syslog(LOG_ERR, <span class="string">&quot;ruptimed: getaddrinfo error: %s&quot;</span>, gai_strerror(err));</span><br><span class="line">        <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;\033[32mDEBUG\033[0m ==&gt; getaddrinfo successfully\n&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (aip = ailist; aip != <span class="literal">NULL</span>; aip = aip-&gt;ai_next)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> ((sockfd = initserver(SOCK_STREAM, aip-&gt;ai_addr, aip-&gt;ai_addrlen, QLEN)) &gt;= <span class="number">0</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            serve(sockfd);</span><br><span class="line">            <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">exit</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="keyword">int</span> <span class="title">initserver</span><span class="params">(<span class="keyword">int</span> type, <span class="keyword">const</span> struct sockaddr *addr, <span class="keyword">socklen_t</span> alen, <span class="keyword">int</span> qlen)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> fd;</span><br><span class="line">    <span class="keyword">int</span> err = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">if</span> ((fd = socket(addr-&gt;sa_family, type, <span class="number">0</span>)) &lt; <span class="number">0</span>)       <span class="comment">// 1. create socket</span></span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    <span class="keyword">if</span> (bind(fd, addr, alen) &lt; <span class="number">0</span>)                          <span class="comment">// 2. bind with address</span></span><br><span class="line">        <span class="keyword">goto</span> errout;</span><br><span class="line">    <span class="keyword">if</span> (type == SOCK_STREAM || type == SOCK_SEQPACKET) &#123;</span><br><span class="line">        <span class="keyword">if</span> (listen(fd, qlen) &lt; <span class="number">0</span>)                          <span class="comment">// 3. listen</span></span><br><span class="line">            <span class="keyword">goto</span> errout;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> fd;</span><br><span class="line"></span><br><span class="line">errout:</span><br><span class="line">    err = errno;</span><br><span class="line">    close(fd);</span><br><span class="line">    errno = err;</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="keyword">void</span> <span class="title">serve</span><span class="params">(<span class="keyword">int</span> sockfd)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span>   clfd, status;</span><br><span class="line">    <span class="keyword">pid_t</span> pid;</span><br><span class="line"></span><br><span class="line">    set_cloexec(sockfd);</span><br><span class="line">    <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">        <span class="keyword">if</span> ((clfd = accept(sockfd, <span class="literal">NULL</span>, <span class="literal">NULL</span>)) &lt; <span class="number">0</span>) &#123;     <span class="comment">// 4. accept</span></span><br><span class="line">            syslog(LOG_ERR, <span class="string">&quot;ruptimed: accpte error: %s&quot;</span>, strerror(errno));</span><br><span class="line">            <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">        &#125; </span><br><span class="line">        <span class="keyword">if</span> ((pid = fork()) &lt; <span class="number">0</span>) &#123;</span><br><span class="line">            syslog(LOG_ERR, <span class="string">&quot;ruptimed: fork error: %s&quot;</span>, strerror(errno));</span><br><span class="line">            <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (pid == <span class="number">0</span>) &#123; <span class="comment">// child process</span></span><br><span class="line">            <span class="keyword">if</span> (dup2(clfd, STDOUT_FILENO) != STDOUT_FILENO || dup2(clfd, STDERR_FILENO) != STDERR_FILENO) &#123;</span><br><span class="line">                syslog(LOG_ERR, <span class="string">&quot;ruptimed: unexpected error&quot;</span>);</span><br><span class="line">                <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            close(clfd);</span><br><span class="line">            execl(<span class="string">&quot;/usr/bin/uptime&quot;</span>, <span class="string">&quot;uptime&quot;</span>, (<span class="keyword">char</span> *)<span class="number">0</span>);</span><br><span class="line">            syslog(LOG_ERR, <span class="string">&quot;ruptimed: unexpected return from exec: %s&quot;</span>, strerror(errno));</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123; <span class="comment">// parent process</span></span><br><span class="line">            close(clfd);</span><br><span class="line">            waitpid(pid, &amp;status, <span class="number">0</span>);</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>实例2：无连接的ruptime</p><p>客户端：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;apue.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;netdb.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;errno.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/socket.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> BUFLEN  128</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> TIMEOUT 20</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">sigalrm</span><span class="params">(<span class="keyword">int</span> signo)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">print_uptime</span><span class="params">(<span class="keyword">int</span> sockfd, struct addrinfo *aip)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span>  n;</span><br><span class="line">    <span class="keyword">char</span> buf[BUFLEN];</span><br><span class="line"></span><br><span class="line">    buf[<span class="number">0</span>] = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">if</span> (sendto(sockfd, buf, <span class="number">1</span>, <span class="number">0</span>, aip-&gt;ai_addr, aip-&gt;ai_addrlen) &lt; <span class="number">0</span>)</span><br><span class="line">        err_sys(<span class="string">&quot;sendto error&quot;</span>);</span><br><span class="line">    alarm(TIMEOUT);</span><br><span class="line">    <span class="keyword">if</span> ((n = recvfrom(sockfd, buf, BUFLEN, <span class="number">0</span>, <span class="literal">NULL</span>, <span class="literal">NULL</span>)) &lt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (errno != EINTR)</span><br><span class="line">            alarm(<span class="number">0</span>);</span><br><span class="line">        err_sys(<span class="string">&quot;recv error&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    alarm(<span class="number">0</span>);</span><br><span class="line">    write(STDOUT_FILENO, buf, n);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">char</span> *argv[])</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">addrinfo</span>  *<span class="title">ailist</span>, *<span class="title">aip</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">addrinfo</span>   <span class="title">hint</span>;</span></span><br><span class="line">    <span class="keyword">int</span>               sockfd, err;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">sigaction</span>  <span class="title">sa</span>;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (argc != <span class="number">2</span>)</span><br><span class="line">        err_quit(<span class="string">&quot;usage: ruptime hostname&quot;</span>);</span><br><span class="line">    </span><br><span class="line">    sa.sa_handler = sigalrm;</span><br><span class="line">    sa.sa_flags = <span class="number">0</span>;</span><br><span class="line">    sigemptyset(&amp;sa.sa_mask);</span><br><span class="line">    <span class="keyword">if</span> (sigaction(SIGALRM, &amp;sa, <span class="literal">NULL</span>) &lt; <span class="number">0</span>)</span><br><span class="line">        err_sys(<span class="string">&quot;sigaction error&quot;</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="built_in">memset</span>(&amp;hint, <span class="number">0</span>, <span class="keyword">sizeof</span>(hint));</span><br><span class="line">    hint.ai_socktype = SOCK_DGRAM;</span><br><span class="line">    hint.ai_canonname = <span class="literal">NULL</span>;</span><br><span class="line">    hint.ai_addr = <span class="literal">NULL</span>;</span><br><span class="line">    hint.ai_next = <span class="literal">NULL</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> ((err = getaddrinfo(argv[<span class="number">1</span>], <span class="string">&quot;ruptime&quot;</span>, &amp;hint, &amp;ailist)) != <span class="number">0</span>)</span><br><span class="line">        err_quit(<span class="string">&quot;getaddrinfo error: %s&quot;</span>, gai_strerror(err));</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (aip = ailist; aip != <span class="literal">NULL</span>; aip = aip-&gt;ai_next) &#123;</span><br><span class="line">        <span class="keyword">if</span> ((sockfd = socket(aip-&gt;ai_family, SOCK_DGRAM, <span class="number">0</span>)) &lt; <span class="number">0</span>) &#123;</span><br><span class="line">            err = errno;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            print_uptime(sockfd, aip);</span><br><span class="line">            <span class="built_in">exit</span>(<span class="number">0</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="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">&quot;can&#x27;t cantact %s: %s\n&quot;</span>, argv[<span class="number">1</span>], strerror(err));</span><br><span class="line">    <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>服务器：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210526183841534.png" alt="image-20210526183841534"></p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210526183901082.png" alt="image-20210526183901082"></p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210526183917257.png" alt="image-20210526183917257"></p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210526183810083.png" alt="image-20210526183810083"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;UNIX环境高级编程&quot;&gt;&lt;a href=&quot;#UNIX环境高级编程&quot; class=&quot;headerlink&quot; title=&quot;UNIX环境高级编程&quot;&gt;&lt;/a&gt;UNIX环境高级编程&lt;/h1&gt;&lt;h2 id=&quot;第1章-UNIX基础知识&quot;&gt;&lt;a href=&quot;#第1章-UNIX基</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>C++ Primer Plus学习记录</title>
    <link href="http://example.com/2021/05/09/C-Primer-Plus/"/>
    <id>http://example.com/2021/05/09/C-Primer-Plus/</id>
    <published>2021-05-09T11:06:02.000Z</published>
    <updated>2021-05-09T11:10:46.050Z</updated>
    
    <content type="html"><![CDATA[<h1 id="C-Primer-Plus"><a href="#C-Primer-Plus" class="headerlink" title="C++ Primer Plus"></a>C++ Primer Plus</h1><h2 id="第1章-预备知识"><a href="#第1章-预备知识" class="headerlink" title="第1章 预备知识"></a>第1章 预备知识</h2><h3 id="1-1-C-简介"><a href="#1-1-C-简介" class="headerlink" title="1.1 C++简介"></a>1.1 C++简介</h3><p>C++融合了3种编程方式：</p><ul><li>面向过程编程</li><li>面向对象编程</li><li>泛型编程</li></ul><h3 id="1-2-C-简史"><a href="#1-2-C-简史" class="headerlink" title="1.2 C++简史"></a>1.2 C++简史</h3><h4 id="1-2-1-C语言"><a href="#1-2-1-C语言" class="headerlink" title="1.2.1 C语言"></a>1.2.1 C语言</h4><p>汇编语言是一种低级语言，可以直接控制硬件，速度快，但依赖于特定的处理器所支持的指令集，可移植性差。高级语言致力于解决问题，而不针对特定的硬件。C语言将低级语言的效率、硬件访问能力和高级语言的通用性、可移植性融合在一起，依赖于不同平台的编译器将C语言源代码翻译为特定机器上的内部代码。</p><h4 id="1-2-2-C语言编程原理"><a href="#1-2-2-C语言编程原理" class="headerlink" title="1.2.2 C语言编程原理"></a>1.2.2 C语言编程原理</h4><p>程序 = 数据 + 算法。</p><p>过程化编程、结构化编程、自顶向下的设计。</p><h4 id="1-2-3-面向对象编程"><a href="#1-2-3-面向对象编程" class="headerlink" title="1.2.3 面向对象编程"></a>1.2.3 面向对象编程</h4><p>OOP强调数据，其理念是设计与问题的本质特性相对应的数据格式，将数据和方法合并为类定义，有助于创建可重用的代码。</p><p>类、对象、抽象、封装、继承、多态。</p><h4 id="1-2-4-C-和泛型编程"><a href="#1-2-4-C-和泛型编程" class="headerlink" title="1.2.4 C++和泛型编程"></a>1.2.4 C++和泛型编程</h4><p>OOP强调的是编程的数据方面，而泛型编程强调的是独立于特定数据类型。</p><h4 id="1-2-5-C-的起源"><a href="#1-2-5-C-的起源" class="headerlink" title="1.2.5 C++的起源"></a>1.2.5 C++的起源</h4><p>C++诞生于贝尔实验室，Bjarne Stroustrup于20世纪80年代开发出C++。</p><h3 id="1-3-可移植性和标准"><a href="#1-3-可移植性和标准" class="headerlink" title="1.3 可移植性和标准"></a>1.3 可移植性和标准</h3><ul><li>C++98</li><li>C++03</li><li><strong>C++11</strong></li></ul><h3 id="1-4-程序创建的技巧"><a href="#1-4-程序创建的技巧" class="headerlink" title="1.4 程序创建的技巧"></a>1.4 程序创建的技巧</h3><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210322102326322.png" alt="image-20210322102326322"></p><h4 id="1-4-1-预处理"><a href="#1-4-1-预处理" class="headerlink" title="1.4.1 预处理"></a>1.4.1 预处理</h4><p>预处理用于处理宏定义指令、条件编译指令、头文件包含指令等，从源文件生成<code>.i</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++ -E hello.cpp -o hello.i</span><br></pre></td></tr></table></figure><h4 id="1-4-2-编译"><a href="#1-4-2-编译" class="headerlink" title="1.4.2 编译"></a>1.4.2 编译</h4><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">g++ -S hello.i -o hello.s</span><br></pre></td></tr></table></figure><h4 id="1-4-3-汇编"><a href="#1-4-3-汇编" class="headerlink" title="1.4.3 汇编"></a>1.4.3 汇编</h4><p>汇编过程实际上指把汇编语言代码翻译成目标机器指令的过程。对于被翻译系统处理的每一个C/C++语言源程序，都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。</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++ -c hello.s -o hello.o</span><br></pre></td></tr></table></figure><h4 id="1-4-4-链接"><a href="#1-4-4-链接" class="headerlink" title="1.4.4 链接"></a>1.4.4 链接</h4><p>由汇编程序生成的目标文件并不能立即就被执行，其中可能还有许多没有解决的问题。例如，某个源文件中的函数可能引用了另一个源文件中定义的某个符号（如变量或者函数调用等）；在程序中可能调用了某个库文件中的函数，等等。所有的这些问题，都需要经链接程序的处理方能得以解决。</p><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">g++ hello.o -o hello(.exe)</span><br></pre></td></tr></table></figure><h3 id="1-5-总结"><a href="#1-5-总结" class="headerlink" title="1.5 总结"></a>1.5 总结</h3><p>​        随着计算机的功能越来越强大，计算机程序越来越庞大而复杂。为应对这种挑战，计算机语言也到了改进，以便编程过程更为简单。C语言新增了诸如控制结构和函数等特性，以便更好地控制程序流程，支持结构化和模块化程度更高的方法；而C++增加了对面向对象编程和泛型编程的支持，这有助于提高模块化和创建可重用代码，从而节省编程时间并提高程序的可靠性。</p><h2 id="第2章-开始学习C"><a href="#第2章-开始学习C" class="headerlink" title="第2章 开始学习C++"></a>第2章 开始学习C++</h2><h3 id="2-1-进入C"><a href="#2-1-进入C" class="headerlink" title="2.1 进入C++"></a>2.1 进入C++</h3><h4 id="2-1-1-main函数"><a href="#2-1-1-main函数" class="headerlink" title="2.1.1 main函数"></a>2.1.1 main函数</h4><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210322105440372.png" alt="image-20210322105440372"></p><p><code>main()</code>函数被启动代码调用，而启动代码是由编译器添加到程序中的，是程序和操作系统之间的桥梁。<code>main()</code>函数的函数头描述的是<code>main()</code>和操作系统之间的接口。</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">main</span>()     <span class="comment">// original style</span></span><br></pre></td></tr></table></figure><p>在C语言中，省略返回类型相当于说函数的类型为<code>int</code>，然而C++逐步淘汰了这种用法。</p><p>有些程序员使用下面的函数头，并省略返回语句：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">main</span><span class="params">()</span></span></span><br></pre></td></tr></table></figure><p>这在逻辑上是一致的，因为<code>void</code>返回类型意味着函数不返回任何值。该变体适用于很多系统，但由于它不是当前标准强制的一个选项，因此在有些系统上不能工作。因此，应该避免使用这种格式，而应使用C++标准格式。</p><p>此外，如果编译器到达<code>main()</code>函数末尾时没有遇到返回语句，则认为<code>main()</code>函数以如下语句结尾：</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="keyword">return</span> <span class="number">0</span>;</span><br></pre></td></tr></table></figure><h4 id="2-1-2-C-注释"><a href="#2-1-2-C-注释" class="headerlink" title="2.1.2 C++注释"></a>2.1.2 C++注释</h4><ul><li><p>单行注释</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="comment">// ....</span></span><br></pre></td></tr></table></figure></li><li><p>多行注释</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="comment">/* ... */</span></span><br></pre></td></tr></table></figure></li></ul><h4 id="2-1-3-include"><a href="#2-1-3-include" class="headerlink" title="2.1.3 #include \"></a>2.1.3 #include \<iostream\></h4><p>预处理过程将iostream的文件内容替换这一行内容。</p><h4 id="2-1-4-using-namespace-std"><a href="#2-1-4-using-namespace-std" class="headerlink" title="2.1.4 using namespace std"></a>2.1.4 using namespace std</h4><p>using编译指令使得命名空间std中的所有名称都可以直接使用，而不用加<code>std::</code>前缀。还可以有以下用法：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> std::cin;</span><br><span class="line"><span class="keyword">using</span> std::cout;</span><br><span class="line"><span class="keyword">using</span> std::endl;</span><br></pre></td></tr></table></figure><h4 id="2-1-5-cout"><a href="#2-1-5-cout" class="headerlink" title="2.1.5 cout"></a>2.1.5 cout</h4><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210322111235070.png" alt="image-20210322111235070"></p><h3 id="2-2-C-语句"><a href="#2-2-C-语句" class="headerlink" title="2.2 C++语句"></a>2.2 C++语句</h3><h4 id="2-2-1-声明语句"><a href="#2-2-1-声明语句" class="headerlink" title="2.2.1 声明语句"></a>2.2.1 声明语句</h4><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="keyword">int</span> carrots;</span><br></pre></td></tr></table></figure><p>声明语句提供两项信息：</p><ol><li>需要的内存空间</li><li>给存储单元指定名称</li></ol><p>程序中的声明语句叫做<strong>定义声明</strong>（defining declaration）语句，简称为定义（definition），这意味着它将导致编译器为变量分配内存空间。在较为复杂的情况下，还可能有<strong>引用声明</strong>（reference declaration），这些声明命令计算机使用在其他地方定义的变量。<strong>声明不一定是定义</strong>。</p><p><strong>Hint</strong>: 对于声明变量，C++的做法是尽可能在首次使用变量前声明它。</p><h4 id="2-2-2-赋值语句"><a href="#2-2-2-赋值语句" class="headerlink" title="2.2.2 赋值语句"></a>2.2.2 赋值语句</h4><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">carrots = <span class="number">25</span>;</span><br></pre></td></tr></table></figure><p><code>=</code>为赋值运算符，赋值语句将值赋给存储单元，赋值将从右向左进行。</p><h3 id="2-3-其他C-语句"><a href="#2-3-其他C-语句" class="headerlink" title="2.3 其他C++语句"></a>2.3 其他C++语句</h3><p><code>cin</code>不读取空白字符，需要使用<code>cin.get()</code>处理回车。</p><p><code>cin</code>是<code>istream</code>类的对象，<code>cout</code>是<code>ostream</code>类的对象。对象之于类，如同变量之于类型。类描述了一种数据类型的全部属性（包括可使用它执行的操作），对象是根据这些描述创建的实体。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210322113721880.png" alt="image-20210322113721880"></p><h3 id="2-4-函数"><a href="#2-4-函数" class="headerlink" title="2.4 函数"></a>2.4 函数</h3><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210322113749849.png" alt="image-20210322113749849"></p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210322113806265.png" alt="image-20210322113806265"></p><h4 id="2-4-1-函数原型"><a href="#2-4-1-函数原型" class="headerlink" title="2.4.1 函数原型"></a>2.4.1 函数原型</h4><p><strong>C++程序应当为程序中使用的每个函数提供原型</strong>。在使用函数之前，C++编译器必须知道函数的参数类型和返回值类型。也就是说，函数是返回整数、字符、小数、有罪裁决还是别的什么东西？如果缺少这些信息，编译器将不知道如何解释返回值。C++提供这种信息的方式就是使用函数原型语句。</p><h4 id="2-4-2-自定义函数"><a href="#2-4-2-自定义函数" class="headerlink" title="2.4.2 自定义函数"></a>2.4.2 自定义函数</h4><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210322121803780.png" alt="image-20210322121803780"></p><h3 id="2-5-总结"><a href="#2-5-总结" class="headerlink" title="2.5 总结"></a>2.5 总结</h3><p>C++多种类型的语句：</p><ul><li>声明语句：声明函数中使用的变量的名称和类型。</li><li>赋值语句：使用赋值运算符<code>=</code>给变量赋值。</li><li>消息语句：将消息发送给对象，激发某种行动。</li><li>函数调用：执行函数。被执行的函数执行完毕后，程序返回到函数调用语句后面的语句。</li><li>函数原型：声明函数的返回类型、函数接受的参数数量和类型。</li><li>返回语句：将一个值从被调用的函数那里返回到调用函数中。</li></ul><h2 id="第3章-处理数据"><a href="#第3章-处理数据" class="headerlink" title="第3章 处理数据"></a>第3章 处理数据</h2><p>本章介绍C++内置的数据类型。内置的C++类型分为两组：<strong>基本类型</strong>和<strong>复合类型</strong>。本章介绍基本类型，即整数和浮点数。</p><h3 id="3-1-简单变量"><a href="#3-1-简单变量" class="headerlink" title="3.1 简单变量"></a>3.1 简单变量</h3><p>为把信息存储在计算机中，程序必须记录3个基本属性：</p><ul><li>存储何种类型的信息</li><li>信息将存储在哪里</li><li>要存储什么值</li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> braincount;</span><br><span class="line">braincount = <span class="number">5</span>;</span><br></pre></td></tr></table></figure><p>这些语句告诉程序，它正在存储整数，并使用名称<code>braincount</code>来表示该整数的值（这里为5）。实际上，程序将找到一块能够存储整形的内存，将该内存单元 标记为<code>braincount</code>，并将5复制到该内存单元中，然后你可以通过<code>braincount</code>来访问该内存单元。</p><h4 id="3-1-1-变量名"><a href="#3-1-1-变量名" class="headerlink" title="3.1.1 变量名"></a>3.1.1 变量名</h4><p>C++<strong>命名规则</strong>：</p><ul><li>在名称中只能使用数字、字母、下划线<code>_</code>。</li><li>名称的第一个字母不能是数字。</li><li>名称区分大小写。</li><li>不能将C++关键字用作名称。</li><li>以两个下划线或下划线和大写字母打头的名称被保留给实现（编译器以及其使用的资源）使用。以一个下划线开头的名称被保留给实现，用作全局标识符。</li><li>C++对于名称的长度没有限制，名称中的所有字符都有意义，但有些平台有长度限制。</li></ul><p>关于倒数第二点，像<code>__time_stop</code>或<code>_Donut</code>这样的名称不会导致编译器错误，而会导致行为的不确定性。不出现编译器错误的原因是，这样的名称不是非法的，但要留给实现使用。</p><p>至于变量命名风格，由雇主决定。</p><h4 id="3-1-2-整型"><a href="#3-1-2-整型" class="headerlink" title="3.1.2 整型"></a>3.1.2 整型</h4><p>C++基本整型（按宽度递增的顺序排列）分别是char, short, int, long, long long。其中每种类型都有符号版本和无符号版本。char类型较为特殊，用于表示字符。</p><p>C++提供了一种灵活的标准，确保了最小长度，如下所示：</p><ul><li>short 至少16位</li><li>int 至少与 short 一样长</li><li>long 至少32位，且至少与 int 一样长</li><li>long long 至少64位，且至少与 long 一样长</li></ul><p>通常来讲，char为8位，short为16位，int为32位，long在windows上为32位，在Linux上跟随系统的位数，long long为64位。</p><p>以上均为符号类型，意味着可以取正，也可以取负。取值范围从$-2^{n-1}$到$2^{n-1}-1$，$n$为位数。无符号版本的取值范围为$0$到$2^{n}-1$。</p><p>头文件<code>climits</code>提供了关于整型限制信息，包括位数、取值范围。</p><div class="table-container"><table><thead><tr><th>符号常量</th><th>表示</th></tr></thead><tbody><tr><td>CHAR_BIT</td><td>char的位数</td></tr><tr><td>CHAR_MAX</td><td>char的最大值</td></tr><tr><td>CHAR_MIN</td><td>char的最小值</td></tr><tr><td>SCHAR_MAX</td><td>signed char的最大值</td></tr><tr><td>SCHAR_MIN</td><td>signed char的最小值</td></tr><tr><td>UCHAR_MAX</td><td>unsigned char的最大值</td></tr><tr><td>SHRT_MAX</td><td>short的最大值</td></tr><tr><td>SHRT_MIN</td><td>short的最小值</td></tr><tr><td>USHRT_MAX</td><td>unsigned short的最大值</td></tr><tr><td>INT_MAX</td><td>int的最大值</td></tr><tr><td>INT_MIN</td><td>int的最小值</td></tr><tr><td>UINT_MAX</td><td>unsigned int的最大值</td></tr><tr><td>LONG_MAX</td><td>long的最大值</td></tr><tr><td>LONG_MIN</td><td>long的最小值</td></tr><tr><td>ULONG_MAX</td><td>unsigned long的最大值</td></tr><tr><td>LLONG_MAX</td><td>long long的最大值</td></tr><tr><td>LLONG_MIN</td><td>long long的最小值</td></tr><tr><td>ULLONG_MAX</td><td>unsigned long long的最大值</td></tr></tbody></table></div><p>溢出行为：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210322201737608.png" alt="image-20210322201737608"></p><h4 id="3-1-3-初始化"><a href="#3-1-3-初始化" class="headerlink" title="3.1.3 初始化"></a>3.1.3 初始化</h4><p>初始化将声明与赋值结合在一起。C++三种初始化语法：</p><ol><li><p>等号初始化</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> n_int = INT_MAX;</span><br><span class="line"><span class="keyword">int</span> uncles = <span class="number">5</span>;</span><br><span class="line"><span class="keyword">int</span> aunts = uncles;</span><br><span class="line"><span class="keyword">int</span> chairs = aunts + uncles + <span class="number">4</span>;</span><br></pre></td></tr></table></figure></li><li><p>小括号初始化</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="function"><span class="keyword">int</span> <span class="title">wrens</span><span class="params">(<span class="number">432</span>)</span></span>;</span><br></pre></td></tr></table></figure></li><li><p>大括号初始化器（C++11）</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><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">int</span> hamburgers = &#123;<span class="number">24</span>&#125;;  <span class="comment">// set humbrugers to 24</span></span><br><span class="line"><span class="keyword">int</span> emus&#123;<span class="number">7</span>&#125;;            <span class="comment">// set emus to 7</span></span><br><span class="line"><span class="keyword">int</span> rocs = &#123;&#125;;          <span class="comment">// set rocs to 0</span></span><br><span class="line"><span class="keyword">int</span> psychics&#123;&#125;;         <span class="comment">// set psychics to 0</span></span><br></pre></td></tr></table></figure><p>大括号初始化器既可用于单值变量，也可用于数组、结构体，更广泛地讲，可以用于任何类型。可以使用等号，也可以不使用等号。</p></li></ol><h4 id="3-1-4-整型字面值"><a href="#3-1-4-整型字面值" class="headerlink" title="3.1.4 整型字面值"></a>3.1.4 整型字面值</h4><p>整形字面值（常量）是显示地书写地常量，如212或176。C++整型的三种不同的计数方式：</p><ul><li>十进制</li><li>八进制，以数字<code>0</code>开头</li><li>十六进制，以<code>0X</code>或<code>0x</code>开头</li></ul><p>cout对象可以调整输出格式（默认情况下以十进制输出）：</p><ul><li>十进制：dec</li><li>八进制：oct</li><li>十六进制：hex</li></ul><h4 id="3-1-5-C-如何确定常量的类型"><a href="#3-1-5-C-如何确定常量的类型" class="headerlink" title="3.1.5 C++如何确定常量的类型"></a>3.1.5 C++如何确定常量的类型</h4><p>通常情况下，一个整数如1492，编译器将其视为int类型。</p><p>特殊后缀：<code>l</code>或<code>L</code>表示long, <code>ll</code>或<code>LL</code>表示long long，<code>ul</code>或<code>UL</code>表示unsigned long, <code>ull</code>或<code>ULL</code>表示unsigned long long。</p><p>考察长度（对于以下规则，现在的编译器好像已经不太一样）：</p><ul><li>对于不带后缀的十进制，使用下面几种类型中能够存储该数的最小类型来表示：int、long或long long。</li><li>对于不带后缀的八进制或十六进制，使用下面几种类型中能够存储该数的最小类型来表示：int、unsigned int、long、unsigned long、long long或unsigned long long。</li></ul><h4 id="3-1-6-char类型"><a href="#3-1-6-char类型" class="headerlink" title="3.1.6 char类型"></a>3.1.6 char类型</h4><p>char类型归为整型中，占8位即1字节，存储的整型范围从$-128$到$127$，为符号类型，也有无符号类型，取值范围从$0$到$255$。char即可表现为signed char，也可表现为unsigned char，通常情况下表现为signed char，但具体取决于实现。char类型在内存中存储的值为整型，但输出时cout将其转换为ASCII码表中的对应字符。在标准输出输出一个字符使用<code>cout.put()</code>。</p><h5 id="3-1-6-1-转义字符"><a href="#3-1-6-1-转义字符" class="headerlink" title="3.1.6.1 转义字符"></a>3.1.6.1 转义字符</h5><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210322205656252.png" alt="image-20210322205656252"></p><h5 id="3-1-6-2-通用字符名"><a href="#3-1-6-2-通用字符名" class="headerlink" title="3.1.6.2 通用字符名"></a>3.1.6.2 通用字符名</h5><p>C++有一种表示特殊字符的机制，它独立于任何特定的键盘，使用的是通用字符名（universal character name）。通用字符名类似于转义序列，以<code>\u</code>或<code>\U</code>打头，<code>\u</code>后接4位16进制数，<code>\U</code>后接8位16进制数，这些位表示的是字符的ISO 10646码点（Unicode的一种实现）。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> k\u00F6rper;</span><br><span class="line">cout &lt;&lt; <span class="string">&quot;Let them eat g\u00E2teau.\n&quot;</span>;</span><br></pre></td></tr></table></figure><h5 id="3-1-6-3-wchar-t"><a href="#3-1-6-3-wchar-t" class="headerlink" title="3.1.6.3 wchar_t"></a>3.1.6.3 wchar_t</h5><p>程序需要处理的字符集可能无法用一个8位的字节表示，如日文汉字系统。对于这种情况，C++的处理方式有两种。</p><ul><li>如果大型字符集是实现的基本字符集，则编译器厂商可以将char定义为一个16位的字节或更长的字节。</li><li>可以同时实现一个支持小型基本字符集和一个较大的扩展字符集。8位的char可以表示基本字符集，另一种类型wchar_t（宽字符类型）可以表示扩展字符集。</li></ul><p>wchar_t类型是一种整数类型，它有足够的空间，可以表示系统使用的最大扩展字符集。这种类型与另一种整型（底层（underlying）类型）的长度和符号属性相同。对底层类型的选择取决于实现，因此在一个系统中，它可能是unsigned short，而在另一个系统中，则可能是int。</p><p>cin和cout将输入和输出看作是char流，因此不适于用来处理wchar_t类型。iostream头文件的最新版本提供了作用相似的工具——wcin和wcout，可以用于处理wchar_t流。另外，可以通过加上前缀L来知识宽字符常量和宽字符串。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">wchar_t</span> bob = <span class="string">L&#x27;P&#x27;</span>;           <span class="comment">// a wide-character constant</span></span><br><span class="line">wcout &lt;&lt; <span class="string">L&quot;tall&quot;</span> &lt;&lt; endl;     <span class="comment">// outputting a wide-character string</span></span><br></pre></td></tr></table></figure><h5 id="3-1-6-4-char16-t和char32-t"><a href="#3-1-6-4-char16-t和char32-t" class="headerlink" title="3.1.6.4 char16_t和char32_t"></a>3.1.6.4 char16_t和char32_t</h5><p>wchar_t的长度和符号特征随实现而异。C++11新增了类型char16_t和char32_t，<strong>均为无符号的</strong>，长分别为16位和32位。C++11使用前缀u表示char16_t字符常量和字符串常量，如<code>u&#39;C&#39;</code>和<code>u&quot;be good&quot;</code>；并使用前缀U表示char32_t常量，如<code>U&#39;R&#39;</code>和<code>U&quot;dirty rat&quot;</code>。类型char16_t与<code>\u00F6</code>形式的通用字符名匹配，而char32_t与<code>\U0000222B</code>形式的通用字符名匹配。</p><figure class="highlight"><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">char16_t</span> ch1 = <span class="string">u&#x27;q&#x27;</span>;</span><br><span class="line">char32_t ch2 = U&#x27;\U0000222B&#x27;;</span><br></pre></td></tr></table></figure><h4 id="3-1-7-bool类型"><a href="#3-1-7-bool类型" class="headerlink" title="3.1.7 bool类型"></a>3.1.7 bool类型</h4><p>布尔类型的变量值可以为true或false。字面值true和false都可以通过提升转换为int类型，true被转换为1，而false被转换为0。</p><p>任何数字值或指针值都可以被<strong>隐式转换</strong>（即不用显示强制转换）为bool值，任何非零值都被转换为true，而零值被转换为false。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">bool</span> start = <span class="number">-100</span>;   <span class="comment">// start assigned true</span></span><br><span class="line"><span class="keyword">bool</span> stop = <span class="number">0</span>;       <span class="comment">// stop assigned false</span></span><br></pre></td></tr></table></figure><h3 id="3-2-const-限定符"><a href="#3-2-const-限定符" class="headerlink" title="3.2 const 限定符"></a>3.2 const 限定符</h3><p>除了#define预处理命令外，C++有一种更好的处理符号常量的方法，这种方法就是使用const关键字来修改变量声明和初始化。</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="keyword">const</span> <span class="keyword">int</span> Months = <span class="number">12</span>;</span><br></pre></td></tr></table></figure><p>常量被初始化后，其值就被固定了，编译器将不允许修改该常量的值。关键字const叫做限定符，因为它限定了声明的含义。</p><p>使用const定义常量时必须在声明的同时进行初始化值。C++中可以使用const值来声明数组长度。</p><h3 id="3-3-浮点数"><a href="#3-3-浮点数" class="headerlink" title="3.3 浮点数"></a>3.3 浮点数</h3><h4 id="3-3-1-浮点数的类型"><a href="#3-3-1-浮点数的类型" class="headerlink" title="3.3.1 浮点数的类型"></a>3.3.1 浮点数的类型</h4><p>C++有三种浮点类型：</p><ul><li>float</li><li>double</li><li>long double</li></ul><p>这些类型时按它们可以表示的有小数位和允许的指数最小范围来描述的。C和C++对于位数的要求是：float至少32位，double至少48位，且不少于float，long double至少和double一样多。这三种类型的位数可以一样多，然而，通常，float为32位，double为64位，long double为80、96或128位。另外，这三种类型的指数范围至少是-37到37。可以从头文件<code>cfloat</code>中找到系统的限制。float保证6位有效位数，double保证15位有效位数。</p><h4 id="3-3-2-浮点数的优缺点"><a href="#3-3-2-浮点数的优缺点" class="headerlink" title="3.3.2 浮点数的优缺点"></a>3.3.2 浮点数的优缺点</h4><p>优点：</p><ul><li>能表示整数之间的数</li><li>具有缩放因子，它们可以表示的范围大得多</li></ul><p>缺点：</p><ul><li>运算速度比整数慢</li><li>精度降低</li></ul><h4 id="3-3-3-类型分类"><a href="#3-3-3-类型分类" class="headerlink" title="3.3.3 类型分类"></a>3.3.3 类型分类</h4><ul><li>算数类型<ul><li>整型<ul><li>有符号整形：signed char, short, int, long, long long,</li><li>无符号整型：unsigned char, unsigned short, unsighed int, unsigned long, unsigned long long，char16_t，char32_t</li><li>不带符号：bool, wchar_t</li></ul></li><li>浮点型：float, double, long double</li></ul></li></ul><h3 id="3-4-C-算数运算符"><a href="#3-4-C-算数运算符" class="headerlink" title="3.4 C++算数运算符"></a>3.4 C++算数运算符</h3><p>5个基本的算术运算符：</p><ul><li>+</li><li>-</li><li>*</li><li>\ ：如果两个操作数都是整数，则结果位商的整数部分，小数部分被丢弃。</li><li>% ：两个操作数必须为整数。结果的符号跟着左操作数。</li></ul><p>优先级及结合性：</p><p>*、\、%位于同一优先级，+、-位于同一优先级且优先级更低，结合性均为从左往右。</p><h3 id="3-5-类型转换"><a href="#3-5-类型转换" class="headerlink" title="3.5 类型转换"></a>3.5 类型转换</h3><p>C++自动执行很多类型转换：</p><ul><li>将一种算术类型的值赋给另一种算术类型的变量时，C++将对值进行转换；</li><li>表达式中包含不同的类型时，C+=将对值进行转换</li><li>将参数传递给函数时，C++将对值进行转换</li></ul><p>不同情况下的类型转换：</p><ol><li><p>初始化和赋值进行的转换</p><p>C++允许将一种类型的值赋给另一种类型的变量，这样做时，值将被转换为接受变量的类型。将一个值赋给取值范围更大的类型通常不会导致什么问题，只是占用更多的字节而已。然而，将一个很大的long值（如2111222333）赋给float变量将降低精度，因为float只有6位有效数字。因此有些转换是安全的，有些则会带来麻烦。</p><p>| 转换                                                        | 潜在的问题                                                   |<br>| —————————————————————————————- | —————————————————————————————— |<br>| 将较大的浮点类型转换为较小的浮点类型，如将double转换为float | 精度（有效数位）降低，值可能超出目标类型的取值范围，在这种情况下，结果是不确定的 |<br>| 将浮点类型转换为整型                                        | 小数部分丢失，原来的值可能超出目标类型的取值范围，在这种情况下，结果是不确定的 |<br>| 将整型转换为浮点类型                                        | 可能会降低精度                                               |<br>| 将较大的整型转换为较小的整型，如将long转换为short           | 原来的值可能超出目标类型的取值范围，通常只赋值右边的字节     |</p><p>将0赋给bool变量时，将被转换为false；而非零值将被转换为true。</p></li><li><p>以{}方式初始化时进行的转换（C++11）</p><p>C++将使用大括号的初始化称为<strong>初始化列表</strong>（list-initialization），因为这种初始化常用于给复杂的数据类型提供值列表。</p><p>列表初始化<strong>不允许缩窄</strong>（narrowing），即变量的类型可能无法表示赋给它的值。例如，<strong>不允许将浮点型转换为整型</strong>。在不同的整型之间的转换或将整型转换为浮点型可能被允许，条件是<strong>编译器知道目标变量能够正确地存储赋给它地值</strong>。例如，可将long变量初始化为int值，因为long总是至少与int一样长；相反方向的转换可能被允许。只要int能够存储赋给它的long常量。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210325110435608.png" alt="image-20210325110435608"></p></li><li><p>表达式中的转换</p><p>当一个表达式中包含两种不同的算数类型时：</p><ul><li><p>一些类型在出现时便会自动转换</p><p>在计算表达式时，C++将bool、char、unsigned char、signed char 和 short 值转换为 int。这些转换被称为<strong>整型提升</strong>。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">short</span> chickens = <span class="number">20</span>;</span><br><span class="line"><span class="keyword">short</span> ducks = <span class="number">35</span>;</span><br><span class="line"><span class="keyword">short</span> fowl = chickens + ducks;</span><br></pre></td></tr></table></figure><p>第三条语句执行时，先将chickens和ducks提升为整型，做完加操作后转换为short类型赋给fowl。</p><p>C++11版本校验表：</p><p>（1） 如果有一个操作数的类型是long double，则将另一个操作数转换为long double。</p><p>（2） 否则，如果有一个操作数的类型是double，则将另一个操作数转换为double。</p><p>（3） 否则，如果有一个操作数类型是float，则将另一个操作数类型转换为float。</p><p>（4） 否则，说明操作数都是整型，因此执行整型提升。</p><p>（5） 在这种情况下，如果两个操作数都是有符号的或无符号的，且其中一个操作数的级别比另一个低，则转换为级别高的类型。</p><p>（6） 如果一个操作数为有符号，另一个操作数为无符号的，且无符号操作数级别比有符号操作数级别高，则将有符号操作数转换为无符号操作数所属类型。</p><p>（7） 否则，如果有符号类型可表示无符号类型的所有可能值，则将无符号操作数转换为有符号操作数所属类型。</p><p>（8） 否则，将两个操作数都转换为有符号类型的无符号版本。</p><p>所谓级别，对于有符号整型来讲从高到底以此为：long long, long, int, short, signed char。无符号与有符号的排列顺序相同。类型char、sighed char和unsigned char的级别相同。类型bool的级别最低。wchar_t、char16_t、char32_t的级别与其底层类型相同。</p></li><li><p>有些类型在与其他类型同时出现在表达式中时将被转换</p></li></ul></li><li><p>传递参数时的类型转换</p><p>传递参数时的类型传递通常由C++函数原型控制。</p></li><li><p>强制类型转换</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">typeName</span> (value)  <span class="comment">// C++</span></span><br><span class="line">(typeName) value  <span class="comment">// C</span></span><br></pre></td></tr></table></figure><p>C++的想法是，让强制类型转化像函数调用。</p><p>此外，C++还引入了强制类型转换运算符，其中<code>static_cast&lt;&gt;</code>可用于将值从一种数值类型转换为另一种数值类型：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static_cast</span>&lt;<span class="keyword">long</span>&gt; (thorn)</span><br><span class="line"></span><br><span class="line"><span class="keyword">static_cast</span>&lt;typeName) (value)  <span class="comment">// converts value to typeName type</span></span><br></pre></td></tr></table></figure></li></ol><h3 id="3-6-C-11-auto声明"><a href="#3-6-C-11-auto声明" class="headerlink" title="3.6 C++11 auto声明"></a>3.6 C++11 auto声明</h3><p>C++11 新增语法，将auto重新定义，让编译器能够根据初始值的类型推断变量的类型。</p><h2 id="第4章-复合类型"><a href="#第4章-复合类型" class="headerlink" title="第4章 复合类型"></a>第4章 复合类型</h2><h3 id="4-1-数组"><a href="#4-1-数组" class="headerlink" title="4.1 数组"></a>4.1 数组</h3><p>声明数组的通用格式：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">typeName arrayName[arraySize];</span><br></pre></td></tr></table></figure><p>arraySize必须是整型常量或const值，也可以是常量表达式，即其中所有的值在编译时都是已知的，不能是变量，变量的值是在程序运行时设置的。</p><p>有效下标值的重要性：编译器<strong>不会</strong>检查使用的下标是否有效，但是在程序运行后，不合法的下标值将引发问题。</p><p>sizeof运算符用于数组名，得到的是整个数组中的字节数；用于数组元素，得到的是元素的长度（单位为字节）。</p><h4 id="4-1-1-数组的初始化"><a href="#4-1-1-数组的初始化" class="headerlink" title="4.1.1 数组的初始化"></a>4.1.1 数组的初始化</h4><h5 id="1-初始化规则"><a href="#1-初始化规则" class="headerlink" title="1 - 初始化规则"></a>1 - 初始化规则</h5><p>数组名是一个只读变量，因此只能在定义数组时才能使用初始化，此后就不能用了，也不能将一个数组赋给另一个数组：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> cards[<span class="number">4</span>] = &#123;<span class="number">3</span>, <span class="number">6</span>, <span class="number">8</span>, <span class="number">10</span>&#125;;   <span class="comment">// okay</span></span><br><span class="line"><span class="keyword">int</span> hards[<span class="number">4</span>];                   <span class="comment">// okay</span></span><br><span class="line">hand[<span class="number">4</span>] = &#123;<span class="number">5</span>, <span class="number">6</span>, <span class="number">7</span>, <span class="number">9</span>&#125;;         <span class="comment">// not allowed, compile error</span></span><br><span class="line">hand = cards;                   <span class="comment">// not allowed, compile error</span></span><br></pre></td></tr></table></figure><p>初始化数组时，如果提供的值少于数组的数目，则剩下的元素吧内编译器设置为0：</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="keyword">float</span> hotelTips[<span class="number">5</span>] = &#123;<span class="number">5.0</span>, <span class="number">2.5</span>&#125;;  <span class="comment">// okay, the third and later are set to zero</span></span><br></pre></td></tr></table></figure><p>如果初始化时不提供长度，编译器将自动计算长度。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">short</span> things[] = &#123;<span class="number">1</span>, <span class="number">5</span>, <span class="number">3</span>, <span class="number">8</span>&#125;;</span><br></pre></td></tr></table></figure><h5 id="2-C-11初始化方法"><a href="#2-C-11初始化方法" class="headerlink" title="2 - C++11初始化方法"></a>2 - C++11初始化方法</h5><ul><li><p>初始化数组时可以省略<code>=</code>。</p></li><li><p>大括号内可以没有任何东西，这将把所有的元素设置为零。</p></li><li><p>列表初始化禁止缩窄转换</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">long</span> plifs[] = &#123;<span class="number">25</span>, <span class="number">92</span>, <span class="number">3.0</span>&#125;;               <span class="comment">// not allowed, float -&gt; int</span></span><br><span class="line"><span class="keyword">char</span> slifs[<span class="number">4</span>] = &#123;<span class="string">&#x27;h&#x27;</span>, <span class="string">&#x27;i&#x27;</span>, <span class="number">1122011</span>, <span class="string">&#x27;\0&#x27;</span>&#125;;  <span class="comment">// not allowed, int -&gt; char</span></span><br><span class="line"><span class="keyword">char</span> tlifs[<span class="number">4</span>] = &#123;<span class="string">&#x27;h&#x27;</span>, <span class="string">&#x27;i&#x27;</span>, <span class="number">112</span>, <span class="string">&#x27;\0&#x27;</span>&#125;;      <span class="comment">// allowed</span></span><br></pre></td></tr></table></figure></li></ul><h3 id="4-2-字符串"><a href="#4-2-字符串" class="headerlink" title="4.2 字符串"></a>4.2 字符串</h3><p>C++两种字符串：C风格字符串和string类库</p><p>C风格字符串使用字符数组存储，以<code>\0</code>标识字符串的结束。区分字符数组和字符串。处理字符串是根据空字符的位置，而不是数组的长度。</p><p><strong>字符串常量实际表示的是字符串所在的内存地址</strong>，因此不能将字符串赋给字符变量。使用sizeof对字符串字面值进行运算将返回字符串所有字符的总字节数（包括末尾的<code>\0</code>），而不是一个指针的大小。</p><p>算字符串长度不把<code>\0</code>纳入长度计算中，数组的长度要大于等于strlen(str) + 1。</p><h4 id="4-1-字符串输入"><a href="#4-1-字符串输入" class="headerlink" title="4.1 字符串输入"></a>4.1 字符串输入</h4><h5 id="4-1-1-std-cin"><a href="#4-1-1-std-cin" class="headerlink" title="4.1.1 std::cin"></a>4.1.1 std::cin</h5><p>cin读取输入时，以空白字符（空格、回车、换行等）为结束标志。当碰到空白字符时，cin停止读取，并将剩下的留在输入队列中，为之前读取的字符串加上<code>\0</code>。<strong>cin可以读取输入队列中留下的空字符并丢弃</strong>，cin.geline和cin.get做不到。</p><h5 id="4-1-2-std-cin-getline"><a href="#4-1-2-std-cin-getline" class="headerlink" title="4.1.2 std::cin.getline()"></a>4.1.2 std::cin.getline()</h5><p>cin.getline()函数读取整行，它使用通过回车键输入的换行符来确定输入结尾，然后丢弃换行符。该函数接受两个参数，一个是用来存储字符串的数组的名称，另一个是要读取的字符数，如果该参数为20，那最多读取19个字符，余下的空间用于存储自动在结尾处添加的空字符。函数在读取到指定数目的字符或遇到换行符时停止读取。</p><h5 id="4-1-3-std-cin-get"><a href="#4-1-3-std-cin-get" class="headerlink" title="4.1.3 std::cin.get()"></a>4.1.3 std::cin.get()</h5><p>cin.get()是一个重载的函数，如果不提供任何参数，将读取一个字符并返回。如果第一个参数是字符串数组，第二个参数为读取的字符，则与cin.getline()一样，但是将回车保留在输入队列中，因此需要使用cin.get()进行处理。</p><h5 id="4-1-4-空行和其他问题"><a href="#4-1-4-空行和其他问题" class="headerlink" title="4.1.4 空行和其他问题"></a>4.1.4 空行和其他问题</h5><p>getline遇到空行时当作空字符串处理，读取的字符串长度为0，第1个字符为<code>\0</code>。get()读取空行后将设置失效位（failbit），这意味着接下来的输入将被阻断，但可以使用cin.clear()来恢复。需要注意的是，即便恢复了输入，但是换行符还在队列中，如果不处理换行符，还是无法继续使用get(str, size)读取。</p><p>另一个潜在的问题是，输入字符串可能比分配的空间长。如果输入行包含的字符数比指定的多，则getline()和get()将把剩余的字符留在输入队列中，而getline()还会设置失效位，并关闭后面的输入。</p><h3 id="4-3-string类简介"><a href="#4-3-string类简介" class="headerlink" title="4.3 string类简介"></a>4.3 string类简介</h3><p>在很多方面，使用string对象的方式与使用C风格字符串的方式相同：</p><ul><li>可以使用C-风格字符串来初始化string对象</li><li>可以使用cin来将标准输入存储到string对象中</li><li>可以使用cout来显示string对象</li><li>可以使用数组表示法来访问存储在string对象中的字符</li></ul><p>string对象声明为简单变量，而不是数组。类设计让程序能够自动处理string的大小，使用string对象更方便，也更安全。</p><h4 id="4-3-1-复制、拼接、附加和长度"><a href="#4-3-1-复制、拼接、附加和长度" class="headerlink" title="4.3.1 复制、拼接、附加和长度"></a>4.3.1 复制、拼接、附加和长度</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><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;cstring&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;string&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">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="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line">    <span class="keyword">char</span> charr1[<span class="number">20</span>];</span><br><span class="line">    <span class="keyword">char</span> charr2[<span class="number">20</span>] = <span class="string">&quot;jaguar&quot;</span>;</span><br><span class="line">    string str1;</span><br><span class="line">    string str2 = <span class="string">&quot;panther&quot;</span>;   <span class="comment">// 赋值</span></span><br><span class="line"></span><br><span class="line">    str1 = str2;               <span class="comment">// 复制</span></span><br><span class="line">    <span class="built_in">strcpy</span>(charr1, charr2);</span><br><span class="line"></span><br><span class="line">    str1 += <span class="string">&quot; paste&quot;</span>;          <span class="comment">// 附加</span></span><br><span class="line">    <span class="built_in">strcat</span>(charr1, <span class="string">&quot; juice&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">int</span> len1 = str1.<span class="built_in">size</span>();    <span class="comment">// 长度</span></span><br><span class="line">    <span class="keyword">int</span> len2 = <span class="built_in">strlen</span>(charr1);</span><br><span class="line"></span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;The string &quot;</span> &lt;&lt; str1 &lt;&lt; <span class="string">&quot; contains &quot;</span> &lt;&lt; len1 &lt;&lt; <span class="string">&quot; characters.\n&quot;</span>;</span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;The string &quot;</span> &lt;&lt; charr1 &lt;&lt; <span class="string">&quot;contains &quot;</span> &lt;&lt; len2 &lt;&lt; <span class="string">&quot; characters.\n&quot;</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><h4 id="4-3-2-string的I-O操作"><a href="#4-3-2-string的I-O操作" class="headerlink" title="4.3.2 string的I/O操作"></a>4.3.2 string的I/O操作</h4><p>getline函数从输入流读取一行放入string对象中，且丢弃结尾的换行符。使用getline函数读取输入字符串到string对象无需担心长度的问题。</p><h4 id="4-3-3-其他形式的字符串"><a href="#4-3-3-其他形式的字符串" class="headerlink" title="4.3.3 其他形式的字符串"></a>4.3.3 其他形式的字符串</h4><ul><li><p>wchar_t</p></li><li><p>char16_t</p></li><li><p>char32_t</p><figure class="highlight c++"><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">wchar_t</span> title[] = <span class="string">L&quot;Chief Astrogator&quot;</span>;</span><br><span class="line"><span class="keyword">char16_t</span> name[] = <span class="string">u&quot;Felonia Ripova&quot;</span>;</span><br><span class="line"><span class="keyword">char32_t</span> car[] = <span class="string">U&quot;Humber Super Snipe&quot;</span>;</span><br></pre></td></tr></table></figure></li><li><p>Unicode字符编码方案UTF-8</p><p>在这种方案中，根据编码的数字值，字符可能存储为1~4个八位组。C++使用前缀u8来表示这种类型的字符串字面值。</p></li><li><p>原始(raw)字符串</p><p><code>cout &lt;&lt; R&quot;(Jim &quot;King&quot; Tutt uses &quot;\n&quot; instead of endl.)&quot; &lt;&lt; &#39;\n&#39;;</code>将显示<code>Jim &quot;King&quot; Tutt uses \n instead of endl.</code></p><p>也就是说<code>R&quot;(......)&quot;</code>是原始字符串的标志，如果想在字符串中显示<code>)&quot;</code>，可以使用<code>R&quot;+*(......)+*&quot;</code>。这是自定义定界符，自定义定界符时，在默认定界符之间添加任意数量的基本字符，但空格、左括号、右括号、斜杠和控制字符（如制表符和换行符）除外。</p><p>可将前缀R与其他字符串前缀结合使用，以标识wchar_t等类型的原始字符串，即<code>RL</code>或<code>LR</code>。</p></li></ul><h3 id="4-4-结构体简介"><a href="#4-4-结构体简介" class="headerlink" title="4.4 结构体简介"></a>4.4 结构体简介</h3><p>一个结构体可以存储多种类型的数据。C++允许在声明结构体变量时省略关键字struct。定义结构体是在创造一种新的数据类型。</p><p>定义结构体的描述可以在外部，也可以在函数内部，后者只能在该函数中使用该结构体。C++不提倡使用外部变量，但提倡使用外部结构声明。</p><h4 id="4-4-1-结构体变量的初始化"><a href="#4-4-1-结构体变量的初始化" class="headerlink" title="4.4.1 结构体变量的初始化"></a>4.4.1 结构体变量的初始化</h4><p>使用初始化列表初始化：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">inflatable</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">char</span> name[<span class="number">20</span>];</span><br><span class="line">    <span class="keyword">float</span> volume;</span><br><span class="line">    <span class="keyword">double</span> price;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">inflatable a = &#123;<span class="string">&quot;Carlos Leo&quot;</span>, <span class="number">30.4f</span>, <span class="number">20.0</span>&#125;;</span><br><span class="line">inflatable b &#123;<span class="string">&quot;Van Gandi&quot;</span>, <span class="number">45</span>, <span class="number">23</span>&#125;;</span><br><span class="line">inflabable c &#123;&#125;;</span><br></pre></td></tr></table></figure><p>大括号内未包含任何东西时，各成员都将被设置为0，name的每个字节都将设置为0，volume和price都被设置为0。</p><h4 id="4-4-2-结构属性"><a href="#4-4-2-结构属性" class="headerlink" title="4.4.2 结构属性"></a>4.4.2 结构属性</h4><ul><li><p><code>.</code>访问结构体成员</p></li><li><p><code>=</code>可以赋值，这样结构中的每个成员都将设置为另一个结构中相应成员的值，即使成员是数组。注意，它并不是简单的对每个成员赋值，而是<strong>深拷贝</strong>。</p></li><li><p>参数传递</p></li><li><p>返回值</p></li><li><p>同时完成结构定义和创建结构变量以及结构变量初始化</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">perks</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">    <span class="keyword">int</span> key_number;</span><br><span class="line">    <span class="keyword">char</span> car[<span class="number">12</span>];</span><br><span class="line">&#125;mr_glitz =</span><br><span class="line">&#123;</span><br><span class="line">  <span class="number">7</span>,</span><br><span class="line">    <span class="string">&quot;Packard&quot;</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></li></ul><p>C++结构体的特性相比于C结构体的特性要更多，如C++结构体除了成员变量之外，还可以有成员函数。</p><h4 id="4-4-3-结构体内存对齐"><a href="#4-4-3-结构体内存对齐" class="headerlink" title="4.4.3 结构体内存对齐"></a>4.4.3 结构体内存对齐</h4><p>结构体内存对齐的三条原则：</p><p>（1）结构体变量的<strong>起始地址</strong>能够被其最宽的成员大小整除。</p><p>（2）结构体<strong>每个成员相对于起始地址的偏移</strong>能够被其<strong>自身大小</strong>整除，如果不能则在前一个成员后面补充字节。</p><p>（3）结构体<strong>总体大小</strong>能够被<strong>最宽的成员的大小</strong>整除，如不能则在后面补充字节。</p><p>空结构体的占1个字节的空间。</p><h4 id="4-4-4-结构体中的位字段"><a href="#4-4-4-结构体中的位字段" class="headerlink" title="4.4.4 结构体中的位字段"></a><span style="color: red">4.4.4 结构体中的位字段</span></h4><p>与C语言一样，C++也允许指定占用特定位数的结构成员，这使得创建与某个硬件设备上的寄存器对应的数据结构非常方便。字段的类型应为整型或枚举，接下来是冒号，冒号后面是一个数字，它指定了使用的位数。可以使用没有名称的字段来提供间距。每个成员都被称为位字段（bit field）。下面是一个例子：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">torgle_register</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">  <span class="keyword">unsigned</span> <span class="keyword">int</span> SN : <span class="number">4</span>;</span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">int</span> : <span class="number">4</span>;</span><br><span class="line">    <span class="keyword">bool</span> goodIn : <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">bool</span> goodTorgle : <span class="number">1</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>可以像通常那样初始化这些字段，还可以使用标准的结构表示法来访问位字段：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">torgle_register tr = &#123; <span class="number">14</span>, <span class="literal">true</span>, <span class="literal">false</span> &#125;;</span><br><span class="line">...</span><br><span class="line"><span class="keyword">if</span> (tr.goodIn)</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>位字段通常用在低级编程中。</p><h3 id="4-5-共用体"><a href="#4-5-共用体" class="headerlink" title="4.5 共用体"></a>4.5 共用体</h3><p>共用体（union）是一种数据格式，它能够存储不同的数据类型，但只能同时存储其中一种类型。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">union</span> <span class="title">one4all</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">  <span class="keyword">int</span> int_val;</span><br><span class="line">    <span class="keyword">long</span> long_val;</span><br><span class="line">    <span class="keyword">double</span> double_val;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>共用体每次只能存储一个值，因此它必须有足够的空间来存储最大的成员，所以，共用体的长度成为其最大成员的长度。</p><p><strong>匿名共用体</strong>没有名称，通常定义在结构体内，由于共用体成员的地址相同，不需要使用中间标识符。共用体常用于节省内存。</p><h3 id="4-6-枚举"><a href="#4-6-枚举" class="headerlink" title="4.6 枚举"></a>4.6 枚举</h3><p>枚举提供了一种定义符号常量的选择。定义枚举类型，是创建了一种新的类型，称为<strong>枚举类型</strong>，以及符号常量，称为<strong>枚举量</strong>。默认情况下，枚举量的值按照顺序从0开始，第一个枚举量的值为0，第二个枚举量的值为1，依此类推。也可以通过显示地指定整数值来覆盖默认值。可以使用枚举名作为类型名来声明变量：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">enum</span> <span class="title">spectrum</span> &#123;</span>red, orange, yellow, green, blue, violet, indigo, ultraviolet&#125;;</span><br><span class="line"></span><br><span class="line">spectrum band; <span class="comment">// band is a variable of type spectrum</span></span><br></pre></td></tr></table></figure><p>枚举变量具有一些特殊的性质：</p><ul><li><p>在不进行强制类型转换时，只能将定义枚举时使用的枚举常量赋值给这种枚举类型的变量</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">band = orange;    <span class="comment">// valid</span></span><br><span class="line">band = <span class="number">2000</span>;      <span class="comment">// invalid, compile error</span></span><br></pre></td></tr></table></figure></li><li><p>对于枚举，只定义了赋值运算符</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">band = orange;        <span class="comment">// valid</span></span><br><span class="line">++band;               <span class="comment">// invalid, compile error</span></span><br><span class="line">band = orange + red;  <span class="comment">// invalid, compile error</span></span><br></pre></td></tr></table></figure></li><li><p>枚举量是整型，因此可以提升为int类型，但int类型不能自动转换为枚举类型</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><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">int</span> color = blue;     <span class="comment">// valid</span></span><br><span class="line">band = <span class="number">3</span>;             <span class="comment">// invalid, compile error</span></span><br><span class="line">color = <span class="number">3</span> + red;      <span class="comment">// valid, red converted to int</span></span><br><span class="line">color = orange + red; <span class="comment">// valid</span></span><br></pre></td></tr></table></figure></li><li><p>如果int的值是有效的，可以通过强制类型转换，将它赋给枚举变量。如果试图对一个不适当的值进行强制类型转换，结果是不确定的。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">band = <span class="built_in">spectrum</span>(<span class="number">3</span>);     <span class="comment">// valid, typecast</span></span><br><span class="line">band = <span class="built_in">spectrum</span>(<span class="number">40003</span>); <span class="comment">// undefined, maybe 40003</span></span><br></pre></td></tr></table></figure></li></ul><p>实际上，枚举更常用来定义相关的符号常量，而不是新类型。如果打算只使用常量，而不创建枚举类型的变量，则<strong>可以省略枚举类型的名称</strong>。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">enum</span> &#123;</span>red, orange, yellow, green, blue, violet, indigo, ultraviolet&#125;;</span><br></pre></td></tr></table></figure><h4 id="4-6-1-设置枚举量的值"><a href="#4-6-1-设置枚举量的值" class="headerlink" title="4.6.1 设置枚举量的值"></a>4.6.1 设置枚举量的值</h4><p>可以使用赋值运算符来显示的设置枚举量的值，而且值可以是long或long long类型，但值只能是常量表达式，不能是变量。</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="class"><span class="keyword">enum</span> <span class="title">bits</span> &#123;</span>one = <span class="number">1</span>, two = <span class="number">2</span>, three, four = <span class="number">4</span>, eight = <span class="number">8</span>&#125;;</span><br></pre></td></tr></table></figure><h4 id="4-6-2-枚举的取值范围"><a href="#4-6-2-枚举的取值范围" class="headerlink" title="4.6.2 枚举的取值范围"></a>4.6.2 枚举的取值范围</h4><p>每个枚举都有取值范围，通过强制类型转换，可以将取值范围中的任何整数值赋给枚举变量，即使这个值不是枚举量。给定如下定义：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">enum</span> <span class="title">bigstep</span> &#123;</span> zero = <span class="number">-6</span>, first, second = <span class="number">100</span>, third&#125;;</span><br></pre></td></tr></table></figure><p><strong>上限</strong>：大于枚举量最大值的最小的2的幂，再减去1。对于枚举类型bigstep，最大值为101(third)，则上限为128 - 1，即127。</p><p><strong>下限</strong>：如果最小枚举量的值大于或等于0，则下限为0。否则，取小于枚举量最小值的最大的2的幂，再加上1。对于枚举类型bigstep，最小值为-6(zero)，则下限为-8 + 1，即-7。</p><p>至于使用sizeof运算符计算其内存空间大小，是根据最大值所对应的最小整型类型的字节数确定的。</p><h3 id="4-7-指针和自由存储空间"><a href="#4-7-指针和自由存储空间" class="headerlink" title="4.7 指针和自由存储空间"></a>4.7 指针和自由存储空间</h3><p>Windows上，指针本身所占内存空间的大小取决于编译器是x86还是x64，二者分别为4字节和8字节。</p><p><strong>警告</strong>：一定要在对指针应用解除引用运算符<code>*</code>之前，将指针初始化为一个确定的、适当的地址。编译器不会检查指针是否有被赋一个正确的地址。</p><p>指针不是整数！！！虽然计算机通常把地址当作整数来处理。从概念上看，指针与整数是截然不同的类型。指针描述的是位置，将两个地址相乘没有任何意义。因此，不能简单地将整数赋给指针。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> *pt;</span><br><span class="line">pt = <span class="number">0xB8000000</span>;         <span class="comment">// 编译错误，类型不匹配</span></span><br><span class="line">pt = (<span class="keyword">int</span> *)<span class="number">0xB8000000</span>;  <span class="comment">// 强制类型转换</span></span><br></pre></td></tr></table></figure><h4 id="4-7-1-new关键字"><a href="#4-7-1-new关键字" class="headerlink" title="4.7.1 new关键字"></a>4.7.1 new关键字</h4><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">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="keyword">int</span> *pi = <span class="keyword">new</span> <span class="keyword">int</span>;</span><br><span class="line"><span class="keyword">double</span> *pd = <span class="keyword">new</span> <span class="keyword">double</span>;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面这段代码有几个要注意的点：</p><ul><li>pi、pd这两个变量是局部变量，存储在栈上，而pi、pd所指向的内存空间存储在堆上。</li><li>pi和pd所占字节数均为指针所占字节数，即4字节或8字节，取决于编译器的架构。而pi和pd所指向的空间即<code>*pi</code>和<code>*pd</code>分别占4字节、8字节。</li></ul><p>new关键字在内存中找到一段合适的内存空间并返回这段空间的首地址。</p><h4 id="4-7-2-delete关键字"><a href="#4-7-2-delete关键字" class="headerlink" title="4.7.2 delete关键字"></a>4.7.2 delete关键字</h4><p>delete关键字用于释放使用new创建的内存空间，二者一定要配套使用，否则将发生内存泄漏。要注意一点：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> *pi = <span class="keyword">new</span> <span class="keyword">int</span>;</span><br><span class="line"><span class="keyword">delete</span> pi;</span><br></pre></td></tr></table></figure><p>上面这段代码中delete只是释放了pi所指向的由new创建的内存空间，但pi这个变量本身并没有被删除。</p><p>几个注意点：</p><ul><li>不要尝试释放已经释放的内存块，C++标准指出，这样做的结果将是不确定的，这意味着什么情况都可能发生。</li><li>不能使用delete来释放声明变量所获得的内存，只能释放使用new分配的内存。</li><li><p>对空指针使用delete是安全的。</p></li><li><p>使用delete的关键在于，将它用于new分配的内存块，也就该内存块的地址，并不是说一定要将delete用于使用new的指针。</p><figure class="highlight c++"><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">int</span> *ps = <span class="keyword">new</span> <span class="keyword">int</span>;</span><br><span class="line"><span class="keyword">int</span> *pq = ps;</span><br><span class="line"><span class="keyword">delete</span> pq;           <span class="comment">// delete with second pointer</span></span><br></pre></td></tr></table></figure></li></ul><h4 id="4-7-3-使用new来创建动态数组"><a href="#4-7-3-使用new来创建动态数组" class="headerlink" title="4.7.3 使用new来创建动态数组"></a>4.7.3 使用new来创建动态数组</h4><p>使用new来创建动态数组，不能使用sizeof来确定动态分配的数组包含的字节数。</p><p>数组名的值不能修改，（我个人猜测数组名实际上是一个const指针），但指针是变量，因此可以修改它的值。</p><h3 id="4-8-指针、数组和指针算数"><a href="#4-8-指针、数组和指针算数" class="headerlink" title="4.8 指针、数组和指针算数"></a>4.8 指针、数组和指针算数</h3><p>指针和数组基本等价的原因在于指针算数和C+=内部处理数组的方式。将整型变量加1，其值将加1。但指针变量加1后，增加的量等于它指向的类型的字节数。值得说明的一点是，C++将数组名解释为地址。</p><p>在多数情况下，C++将数组名解释为数组的第1个元素的地址。将指针变量加1后，其增加的值等于指向的类型占用的字节数。使用数组表示法表示数组元素时，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">arrayname[i] becomes *(arrayname + i)</span><br></pre></td></tr></table></figure><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">pointername[i] becomes *(pointername + i)</span><br></pre></td></tr></table></figure><p><strong>数组名是常量。</strong></p><p>使用sizeof对数组进行运算时，得到的是整个数组的内存大小，而使用sizeof对指针运算时，得到的是单个指针变量所占内存空间的大小。</p><h4 id="4-8-1-数组的地址"><a href="#4-8-1-数组的地址" class="headerlink" title="4.8.1 数组的地址"></a>4.8.1 数组的地址</h4><p>对数组取地址时，数组名不会被解释为其地址。数组名被解释为第一个元素的地址，而对数组名运用地址运算符时，得到的时整个数组的地址：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">short</span> tell[<span class="number">10</span>];</span><br><span class="line">cout &lt;&lt; tell &lt;&lt; endl;    <span class="comment">// display &amp;tell[0]</span></span><br><span class="line">cout &lt;&lt; &amp;tell &lt;&lt; endl;   <span class="comment">// display address of whole array</span></span><br></pre></td></tr></table></figure><p>从数值上讲，这两个地址相同，但从概念上讲，<code>&amp;tell[0]</code>是一个2字节内存块的地址，而<code>&amp;tell</code>是一个20字节内存块的地址。因此<code>tell = 1</code>将地址值加2，而表达式<code>&amp;tell + 1</code>将地址值加20。因此，<code>tell</code>是一个指向short的指针，而<code>&amp;tell</code>是一个指向包含20个元素的数组的指针，而这样的指针必须使用下面的声明：</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"><span class="keyword">short</span></span> (*pas)[<span class="number">20</span>] = &amp;tell;</span><br></pre></td></tr></table></figure><p>如果省略括号，pas将先与[20]结合，导致pas成了一个数组，而数组的每个元素都是指向<code>short</code>的指针。</p><h4 id="4-8-2-指针和C风格字符串"><a href="#4-8-2-指针和C风格字符串" class="headerlink" title="4.8.2 指针和C风格字符串"></a>4.8.2 指针和C风格字符串</h4><p>在cout和多数C++表达式中，char数组名、char指针以及用括号括起的字符串常量都被解释为字符串的第一个字符的地址。</p><p>使用cout输出字符数组、字符串常量、字符指针时，都将输出字符串的内容，为了输出字符串的地址，必须使用强制类型转换<code>void *</code>（<code>int *</code>）也可以。</p><p>将字符串读入程序时，应使用已分配的内存地址。该地址可以是数组名，也可以是使用new初始化过的指针。</p><h4 id="4-8-3-自动存储、静态存储和动态存储"><a href="#4-8-3-自动存储、静态存储和动态存储" class="headerlink" title="4.8.3 自动存储、静态存储和动态存储"></a>4.8.3 自动存储、静态存储和动态存储</h4><ul><li><p>自动存储</p><p>在函数内部定义的常规变量使用<strong>自动存储空间</strong>，被称为<strong>自动变量</strong>（automatic variable），这意味着它们在所属的函数被调用时自动产生，在该函数结束时消亡。实际上，自动变量是一个局部变量，其作用域为包含它的代码块。</p><p>自动变量通常存储在<strong>栈</strong>中。这意味着执行代码块时，其中的变量将依次加入到栈中，而在离开代码块时，将按相反的顺序释放这些变量，这被称为<strong>后进先出</strong>（LIFO）。因此，在程序执行的过程中，栈将不断地增大和缩小。</p><p><span style="color:red">栈帧的概念？</span></p></li><li><p>静态存储</p><p>静态存储是<strong>整个程序执行期间</strong>都存在的存储方式。使变量成为静态的方式有两种：一种是在函数外面定义它，另一种是在声明变量时使用关键字static。</p></li><li><p>动态存储</p><p>new和delete运算符提供了一种比自动变量和静态变量更灵活的方法。它们管理了一个内存池，这在C++中被称为自由存储空间（free space）或<strong>堆</strong>（heap）。该内存池同用于静态变量和自动变量的内存是分开的。数据的生命周期不完全受程序或函数的生存时间控制。</p></li></ul><p>自动存储和静态存储的关键在于这些方法严格地限制了变量的寿命。变量可能存在于程序的整个生命周期，也可能只是在特定函数被执行时存在（自动变量）。new和delete让程序员对程序如何使用内存有更大的控制权。然而，内存管理也更复杂了。在栈中，自动添加和删除机制使得占用的内存总是连续的，但new和delete的相互影响可能导致占用的自由存储区不连续，这使得跟踪新分配内存的位置更困难。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210327170624623.png" alt="image-20210327170624623"></p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210327170642931.png" alt="image-20210327170642931"></p><h4 id="4-8-4-类型组合"><a href="#4-8-4-类型组合" class="headerlink" title="4.8.4 类型组合"></a>4.8.4 类型组合</h4><p>注意一点：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><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"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&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="class"><span class="keyword">struct</span> <span class="title">antarctica_years_end</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">    <span class="keyword">int</span> year;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">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">    antarctica_years_end s01, s02, s03;</span><br><span class="line">    s01.year = <span class="number">1998</span>;</span><br><span class="line">    antarctica_years_end *pa = &amp;s02;</span><br><span class="line">    pa-&gt;year = <span class="number">1999</span>;</span><br><span class="line">    antarctica_years_end trio[<span class="number">3</span>];</span><br><span class="line">    trio[<span class="number">0</span>].year = <span class="number">2003</span>;</span><br><span class="line">    cout &lt;&lt; trio-&gt;year &lt;&lt; endl;</span><br><span class="line">    <span class="keyword">const</span> antarctica_years_end *arp[<span class="number">3</span>] = &#123;&amp;s01, &amp;s02, &amp;s03&#125;;  <span class="comment">// 指针数组</span></span><br><span class="line">    cout &lt;&lt; arp[<span class="number">1</span>]-&gt;year &lt;&lt; endl;</span><br><span class="line">    <span class="keyword">const</span> antarctica_years_end **ppa = arp;                   <span class="comment">// 指向指针的指针</span></span><br><span class="line">    <span class="keyword">auto</span> ppb = arp;                                           <span class="comment">// 自动类型推断</span></span><br><span class="line">    cout &lt;&lt; (*ppa)-&gt;year &lt;&lt; endl;</span><br><span class="line">    cout &lt;&lt; (*(ppb + <span class="number">1</span>))-&gt;year &lt;&lt; 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><h3 id="4-9-数组的替代品"><a href="#4-9-数组的替代品" class="headerlink" title="4.9 数组的替代品"></a>4.9 数组的替代品</h3><h4 id="4-9-1-模板类vector"><a href="#4-9-1-模板类vector" class="headerlink" title="4.9.1 模板类vector"></a>4.9.1 模板类vector</h4><p>模板类vector类类似于string类，也是一种动态数组，可以在运行时阶段设置vector对象的长度，可在末尾附加新数据，还可以在中间插入新数据。实际上，vector使用new和delete来管理内存，但这种工作是自动完成的。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;vector&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">vector&lt;<span class="keyword">int</span>&gt; vi;       <span class="comment">// create a zero-size array of int</span></span><br><span class="line"><span class="keyword">int</span> n;</span><br><span class="line">cin &gt;&gt; n;</span><br><span class="line"><span class="function">vector&lt;<span class="keyword">double</span>&gt; <span class="title">vd</span><span class="params">(n)</span></span>; <span class="comment">// create a n-size array of double</span></span><br></pre></td></tr></table></figure><h4 id="4-9-2-模板类array"><a href="#4-9-2-模板类array" class="headerlink" title="4.9.2 模板类array"></a>4.9.2 模板类array</h4><p>如果您需要的是长度固定的数组，可以使用模板类array类。与数组一样，array对象的长度是固定，也使用栈，而不是自由存储区，因此效率与数组相同，但更方便，更安全。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;array&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">array&lt;<span class="keyword">int</span>, 5&gt; ai;          <span class="comment">// create array object of 5 size</span></span><br><span class="line">array&lt;<span class="keyword">double</span>, 4&gt; ad = &#123;<span class="number">1.2</span>, <span class="number">2.1</span>, <span class="number">3.43</span>, <span class="number">4.3</span>&#125;;</span><br></pre></td></tr></table></figure><p>数组长度不能是变量。</p><p>如果使用数组、array对象、vector对象都使用下标表示法取元素，则编译器均不检查错误的索引。但是array和vector提供了安全的访问访问方法：</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">ai.<span class="built_in">at</span>(<span class="number">1</span>) = <span class="number">2.3</span>;</span><br></pre></td></tr></table></figure><p>使用<code>at()</code>时，将在运期间捕获非法引用，而程序默认中断。这种额外检查的代价是运行时间更长。</p><h2 id="第5章-循环和关系表达式"><a href="#第5章-循环和关系表达式" class="headerlink" title="第5章 循环和关系表达式"></a>第5章 循环和关系表达式</h2><h3 id="5-1-表达式和语句"><a href="#5-1-表达式和语句" class="headerlink" title="5.1 表达式和语句"></a>5.1 表达式和语句</h3><h4 id="5-1-1-表达式"><a href="#5-1-1-表达式" class="headerlink" title="5.1.1 表达式"></a>5.1.1 表达式</h4><p>任何值或任有效的值和运算符的组合都是表达式，每个表达式都有值。C++将赋值表达式的值定义为左侧成员的值。当判定表达式的值这种操作改变了内存中的数据的值时，我们说表达式有<strong>副作用</strong>（side effect）。因此，判定赋值表达式会带来这样的副作用，即修改被赋值着的值。</p><h4 id="5-1-2-语句"><a href="#5-1-2-语句" class="headerlink" title="5.1.2 语句"></a>5.1.2 语句</h4><p>只要加上分号，所有的表达式都可以称为语句，但不一定有编程意义。例如<code>rodents + 6;</code>。</p><h4 id="5-1-3-非表达式和语句"><a href="#5-1-3-非表达式和语句" class="headerlink" title="5.1.3 非表达式和语句"></a>5.1.3 非表达式和语句</h4><p>对任何表达式加上分号都可以称为语句，但是这句话反过来说就不对了。也就是说，从语句中删除分号，并不一定将它转换为表达式，如返回语句、声明语句和for语句。</p><figure class="highlight c++"><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">int</span> toad;</span><br><span class="line">eggs = <span class="keyword">int</span> toad * <span class="number">1000</span>;   <span class="comment">// invalid, not an expression</span></span><br><span class="line">cin &gt;&gt; <span class="keyword">int</span> toad;          <span class="comment">// can&#x27;t combine declaration with cin</span></span><br></pre></td></tr></table></figure><p><code>int toad</code>不是表达式，因为它没有值。</p><h4 id="5-1-4-顺序点"><a href="#5-1-4-顺序点" class="headerlink" title="5.1.4 顺序点"></a>5.1.4 顺序点</h4><p><strong>顺序点(sequence point)是程序执行过程中的一个点，在这里，进入下一步之前将确保对所有的副作用都进行了评估</strong>。如语句<code>;</code>就是一个顺序点。任何完整的表达式末尾都是一个顺序点。完整表达式是指不是另一个更大表达式的子表达式，如表达式语句中的表达式部分以及用作while循环中检测条件的表达式。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span> (guests++ &lt; <span class="number">10</span>)</span><br><span class="line">    cout &lt;&lt; guests &lt;&lt; endl;</span><br></pre></td></tr></table></figure><p>由于<code>guests++ &lt; 10</code>是一个完整的表达式，因此该表达式的末尾是一个顺序点，所以在循环判断结束guests就加了1。</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">y = (<span class="number">4</span> + x++) + (<span class="number">6</span> + x++);</span><br></pre></td></tr></table></figure><p>由于<code>4 + x++</code>不是一个完整的表达式，C++不保证x的值在计算子表达式<code>4 + x++</code>后立刻增加1。整条赋值语句的表达式是一个完整的表达式，分号标示了顺序点，因此C++只保证执行到下一条语句之前，x自加两次。</p><p>cout在显示bool值之前将它们转换为int，但<code>cout.setf(ios::boolalpha)</code>函数调用设置了一个标记，该标记命令cout显示true和false，而不是1和0。</p><h3 id="5-2-复合语句"><a href="#5-2-复合语句" class="headerlink" title="5.2 复合语句"></a>5.2 复合语句</h3><p>代码块由一对花括号和它们包含的语句组成，被视为一条语句，称为复合语句。</p><p>如果在语句块中定义一个新的变量，则仅当程序执行该语句块中的语句时，该变量才存在。执行完该语句块后，变量将被释放。</p><p>如果在一个语句块中声明一个变量，而外部语句块中也有一个同名变量，则在声明位置到内部语句块结束的范围之内，新变量将隐藏旧变量。</p><h3 id="5-3-递增、递减运算符"><a href="#5-3-递增、递减运算符" class="headerlink" title="5.3 递增、递减运算符"></a>5.3 递增、递减运算符</h3><h4 id="5-3-1-前缀格式和后缀格式"><a href="#5-3-1-前缀格式和后缀格式" class="headerlink" title="5.3.1 前缀格式和后缀格式"></a>5.3.1 前缀格式和后缀格式</h4><p>递增、递减运算符分为前缀和后缀版本。前缀版本如<code>++x</code>、<code>--x</code>的值为执行递增或递减后的值，而后缀版本如<code>x++</code>、<code>x--</code>的值为原始的值，并且执行递增或递减。</p><p>此外，前缀格式的效率比后缀版本高。这是因为，用于这样定义前缀函数：将值加1然后返回；但后缀版本首先复制一个副本，将其加1，然后将复制的副本返回。因此，对于类而言，前缀版本比后缀版本效率高。</p><h4 id="5-3-2-递增-递减运算符和指针"><a href="#5-3-2-递增-递减运算符和指针" class="headerlink" title="5.3.2 递增/递减运算符和指针"></a>5.3.2 递增/递减运算符和指针</h4><p>前缀递增、递减和解引用运算符优先级相同，以从右到左进行结合。后缀递增和后缀递减的优先级相同，但比前缀运算符的优先级高，这两个运算符以从左到右的方式进行结合。</p><h3 id="5-4-组合赋值运算符"><a href="#5-4-组合赋值运算符" class="headerlink" title="5.4 组合赋值运算符"></a>5.4 组合赋值运算符</h3><p>每个算数运算符都有其对应的组合赋值运算符（有的叫增强赋值运算符）。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210329103606086.png" alt="image-20210329103606086"></p><h3 id="5-5-逗号运算符"><a href="#5-5-逗号运算符" class="headerlink" title="5.5 逗号运算符"></a>5.5 逗号运算符</h3><p>逗号并不是逗号运算符，例如，下面这个声明语句中的逗号将变量列表中相邻的名称分开：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> i, j;</span><br></pre></td></tr></table></figure><p>C++的逗号运算符特性：</p><ul><li><p>逗号运算符是一个顺序点，确保先计算第一个表达式，然后计算第二个表达式</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">i = <span class="number">20</span>, j = <span class="number">2</span> * i;</span><br></pre></td></tr></table></figure></li><li><p>逗号表达式的值是第二部分的值，如上面表达式的值为40，因为<code>j = 2 * i</code>的值为40。</p></li></ul><p>在所有的运算符中，逗号运算符的优先级是最低的：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cats = <span class="number">17</span>, <span class="number">240</span>;</span><br></pre></td></tr></table></figure><p>上面的语句被解释为<code>(cats = 17), 240</code>，240不起作用。可以使用括号改变这一点：<code>cats = (17, 240);</code>，这样cats的值就是第二部分的值240。</p><h3 id="5-6-关系运算符和关系表达式"><a href="#5-6-关系运算符和关系表达式" class="headerlink" title="5.6 关系运算符和关系表达式"></a>5.6 关系运算符和关系表达式</h3><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210329105433501.png" alt="image-20210329105433501"></p><p>关系运算符比算数运算符的优先级低。</p><p>关系表达式的结果要么为true，要么为false，因此整型提升就就是要么为1，要么为0。</p><h3 id="5-7-for循环"><a href="#5-7-for循环" class="headerlink" title="5.7 for循环"></a>5.7 for循环</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (initialization-expression; test-expression; update-expression)</span><br><span class="line">    body</span><br></pre></td></tr></table></figure><p>for循环的组成部分：</p><ul><li>设置初始值</li><li>执行测试，看看循环是否应当继续进行</li><li>执行循环操作</li><li>更新用于测试的值</li></ul><p>C++语法将整个for看作一条语句，虽然循环体可以包含一条或多条语句。test-expression可以使用任意表达式，C++将把结果强制转换为bool类型。update-expression在每轮循环结束时执行，此时循环体已经执行完毕。</p><p>for循环是入口条件（entry-condition）循环，这意味着在每轮循环之前，都将计算测试表达式的值。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210329093624225.png" alt="image-20210329093624225"></p><h4 id="5-7-1-声明语句表达式"><a href="#5-7-1-声明语句表达式" class="headerlink" title="5.7.1 声明语句表达式"></a>5.7.1 声明语句表达式</h4><p>for循环原始的语法为：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (expression; expression; expression)</span><br><span class="line">    statement;</span><br></pre></td></tr></table></figure><p>以前不可以在初始化部分声明变量，因为声明不是表达式。通过引入声明语句表达式（declaration-statement expression）来合法化。声明语句表达式不带分号声明，只能出现在for语句中。然而，这种调整已经被取消了，代之以将for语句的句法修改成下面这样：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="keyword">for</span>-init-statement condition; expression)</span><br><span class="line">    statement</span><br></pre></td></tr></table></figure><p>for-init-statement后面没有分号，因为其自身就是带分号的语句。对于for-init-statement来说，它既可以是表达式语句，也可以是声明语句。而使用该方式声明的变量，只能在for循环中存在，当程序离开循环后，这种变量将消失。</p><h4 id="5-7-2-基于范围的for循环（C-11）"><a href="#5-7-2-基于范围的for循环（C-11）" class="headerlink" title="5.7.2 基于范围的for循环（C++11）"></a>5.7.2 基于范围的for循环（C++11）</h4><p>C++11新增了一种循环：基于范围（range-based）的for循环。</p><figure class="highlight c++"><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">double</span> prices[<span class="number">5</span>] = &#123;<span class="number">4.99</span>, <span class="number">10.99</span>, <span class="number">6.87</span>, <span class="number">7.99</span>, <span class="number">8.49</span>&#125;;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">double</span> x : prices)</span><br><span class="line">    cout &lt;&lt; x &lt;&lt; std::endl;</span><br></pre></td></tr></table></figure><p>这里的x属于临时变量，要修改数组的元素，需要使用不同的循环变量语法：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="keyword">double</span> &amp;x : prices)</span><br><span class="line">    x = x * <span class="number">0.80</span>;</span><br></pre></td></tr></table></figure><p>还可结合使用基于范围的for循环和初始化列表：</p><figure class="highlight c++"><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">for</span> (<span class="keyword">int</span> x : &#123;<span class="number">3.</span> <span class="number">5</span>, <span class="number">2</span>, <span class="number">8</span>, <span class="number">6</span>&#125;)</span><br><span class="line">    cout &lt;&lt; x &lt;&lt; <span class="string">&quot; &quot;</span>;</span><br><span class="line">cout &lt;&lt; <span class="string">&#x27;\n&#x27;</span>;</span><br></pre></td></tr></table></figure><h3 id="5-8-while循环"><a href="#5-8-while循环" class="headerlink" title="5.8 while循环"></a>5.8 while循环</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span> (test-condition)</span><br><span class="line">    body</span><br></pre></td></tr></table></figure><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210329110716611.png" alt="image-20210329110716611"></p><p>ctime头文件中提供了时间类型，常用的是time_t和clock_t。clock_t的单位是CPU的时钟周期。</p><h4 id="5-8-1-typedef"><a href="#5-8-1-typedef" class="headerlink" title="5.8.1 typedef"></a>5.8.1 typedef</h4><p>typedef用于类类型建立别名，但是typedef不会创建新类型，而只是为已有的类型建立一个新名称。</p><h3 id="5-9-do-while循环"><a href="#5-9-do-while循环" class="headerlink" title="5.9 do while循环"></a>5.9 do while循环</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">do</span></span><br><span class="line">    body</span><br><span class="line"><span class="keyword">while</span> (test-expression);</span><br></pre></td></tr></table></figure><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210329112109266.png" alt="image-20210329112109266"></p><p>do while不同于另外两种循环，因为它是出口条件（exit condition）循环，先执行循环体，然后再判定测试表达式。</p><h3 id="5-10-文件尾条件"><a href="#5-10-文件尾条件" class="headerlink" title="5.10 文件尾条件"></a>5.10 文件尾条件</h3><p>检测到EOF（end of file）后，cin将两位（eofbit和failbit）都设置为1。可以通过成员函数<code>eof()</code>来查看eofbit是否被设置。同样，如果eofbit和failbit被设置为1，<code>fail()</code>成员函数返回true，否则返回false。</p><h4 id="5-10-1-常见的字符输入做法"><a href="#5-10-1-常见的字符输入做法" class="headerlink" title="5.10.1 常见的字符输入做法"></a>5.10.1 常见的字符输入做法</h4><p>每次读取一个字符，知道遇到EOF的输入循环的基本设计如下：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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">cin.<span class="built_in">get</span>(ch);</span><br><span class="line"><span class="keyword">while</span> (cin.<span class="built_in">fail</span>() != <span class="literal">false</span>)</span><br><span class="line">&#123;</span><br><span class="line">    ...</span><br><span class="line">        cin.<span class="built_in">get</span>(ch);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上述循环判断也可以改写为：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span> (!cin.<span class="built_in">fail</span>())</span><br></pre></td></tr></table></figure><p>方法cin.get(char)的返回值是一个cin对象，然而，istream类提供了一个可以将istream对象转换为bool值得函数；当cin出现在需要bool值的地方，该转换函数将被调用。另外，如果最后一次读取成功了，则转换得到的bool值为true，否则为false。这意味着可以将上述while测试改写成这样：</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="keyword">while</span> (cin)</span><br></pre></td></tr></table></figure><p>这比<code>!cin.fail()</code>或<code>!cin.eof()</code>更通用，因为它可以检测到其他失败原因，如磁盘故障。最后，由于cin.get(char)返回cin，因此可以将循环简化成这种格式：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><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">while</span> (cin.<span class="built_in">get</span>(ch))</span><br><span class="line">&#123;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>另外，cin.get()还可以这么用：</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="keyword">char</span> ch = cin.<span class="built_in">get</span>();</span><br></pre></td></tr></table></figure><p>因此，循环还可以这样写：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">char</span> ch = cin.<span class="built_in">get</span>();</span><br><span class="line"><span class="keyword">while</span> (ch != EOF)</span><br><span class="line">&#123;</span><br><span class="line">    ...</span><br><span class="line">    ch = cin.<span class="built_in">get</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>EOF不表示输入中的字符，而是指出没有字符，其定义在stdio.h中，即<code>#define EOF -1</code>。</p><p>简化形式：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span> ((ch = cin.<span class="built_in">get</span>()) != EOF)</span><br><span class="line">&#123;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210329124800907.png" alt="image-20210329124800907"></p><h3 id="5-11-嵌套循环和二维数组"><a href="#5-11-嵌套循环和二维数组" class="headerlink" title="5.11 嵌套循环和二维数组"></a>5.11 嵌套循环和二维数组</h3><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210329181811484.png" alt="image-20210329181811484"></p><h2 id="第6章-分支语句和逻辑运算符"><a href="#第6章-分支语句和逻辑运算符" class="headerlink" title="第6章 分支语句和逻辑运算符"></a>第6章 分支语句和逻辑运算符</h2><h3 id="6-1-if语句"><a href="#6-1-if语句" class="headerlink" title="6.1 if语句"></a>6.1 if语句</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (test-condition)</span><br><span class="line">    statement</span><br></pre></td></tr></table></figure><p>if测试条件也将被强制转换成bool值。整个if语句被视为一条语句。</p><p>通常情况下，测试条件都是关系表达式。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210330080210982.png" alt="image-20210330080210982"></p><h4 id="6-1-1-if-else语句"><a href="#6-1-1-if-else语句" class="headerlink" title="6.1.1 if-else语句"></a>6.1.1 if-else语句</h4><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210330080328793.png" alt="image-20210330080328793"></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><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> (test-condition)</span><br><span class="line">    statement1</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">    statement2</span><br></pre></td></tr></table></figure><p>整个if-else结构被视为一条语句。</p><h4 id="6-1-2-if-elseif-else结构"><a href="#6-1-2-if-elseif-else结构" class="headerlink" title="6.1.2 if-elseif-else结构"></a>6.1.2 if-elseif-else结构</h4><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (test-condition1)</span><br><span class="line">    statement1</span><br><span class="line"><span class="keyword">else</span> <span class="keyword">if</span> (test-condition2)</span><br><span class="line">    statement2</span><br><span class="line">...</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">    statementn</span><br></pre></td></tr></table></figure><h3 id="6-2-逻辑表达式"><a href="#6-2-逻辑表达式" class="headerlink" title="6.2 逻辑表达式"></a>6.2 逻辑表达式</h3><p>C++提供3中逻辑运算符，来组合或修改已有的表达式。</p><ul><li>逻辑或<code>||</code></li><li>逻辑与<code>&amp;&amp;</code></li><li>逻辑非<code>!</code></li></ul><p>运算符优先级：</p><p>逻辑非<code>!</code>高于所有关系运算符和算数运算符，而逻辑与<code>&amp;&amp;</code>高于逻辑或<code>||</code>。</p><h3 id="6-3-字符函数库cctype"><a href="#6-3-字符函数库cctype" class="headerlink" title="6.3 字符函数库cctype"></a>6.3 字符函数库cctype</h3><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210330124442757.png" alt="image-20210330124442757"></p><h3 id="6-4-条件运算符"><a href="#6-4-条件运算符" class="headerlink" title="6.4 条件运算符?:"></a>6.4 条件运算符<code>?:</code></h3><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">expression1 ? expression2 : expression3</span><br></pre></td></tr></table></figure><h3 id="6-5-switch语句"><a href="#6-5-switch语句" class="headerlink" title="6.5 switch语句"></a>6.5 switch语句</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in"><span class="keyword">switch</span></span>(integer-expression)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">case</span> label1: <span class="built_in">statement</span>(s);</span><br><span class="line">    <span class="keyword">case</span> label2: <span class="built_in">statement</span>(s);</span><br><span class="line">   ....</span><br><span class="line">    <span class="keyword">default</span>: <span class="built_in">statement</span>(s);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>integer-expression必须是一个结果为整数值的表达式，另外，每个标签都必须是整数常量表达式，最常见的是int或char常量（如1或’q’），也可以是枚举量。</p><p>switch语句中的每一个case标签都必须是一个单独的值，另外，这个值必须是整数，因此switch无法处理浮点测试。另外，case标签值还必须是常量。</p><h4 id="6-5-1-枚举量用作标签"><a href="#6-5-1-枚举量用作标签" class="headerlink" title="6.5.1 枚举量用作标签"></a>6.5.1 枚举量用作标签</h4><p>当switch语句将int值和枚举常量标签进行比较时，将枚举量提升为int。另外，在while循环测试条件中，也会将枚举量提升为int类型。</p><h3 id="6-6-break和continue"><a href="#6-6-break和continue" class="headerlink" title="6.6 break和continue"></a>6.6 break和continue</h3><p>break使得程序结束循环，而continue使得程序跳过当前循环，进入下一轮循环。</p><h3 id="6-7-读取数字的循环"><a href="#6-7-读取数字的循环" class="headerlink" title="6.7 读取数字的循环"></a>6.7 读取数字的循环</h3><p>输入错误和文件尾EOF都将导致cin返回false。对于下面的程序：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> n;</span><br><span class="line">cin &gt;&gt; n;</span><br></pre></td></tr></table></figure><p>如果用户输入一个单词，而不是一个数，则类型不匹配，此时：</p><ul><li>n值保持不变</li><li>不匹配的输入将被留在输入队列中</li><li>cin对象的一个错误标记被设置</li><li>对cin方法的调用将返回false（如果被转换为bool类型）</li></ul><p>方法返回false意味着可以使用非数字输入来结束读取数字的循环。非数字输入设置错误标志意味着必须重置该标记，程序才能继续读取输入。clear()方法重置错误输入标记，同时也重置文件尾（EOF条件）。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210330130349296.png" alt="image-20210330130349296"></p><p>这段代码就是错误处理，把本行剩下的输入都读完，且直到输入正确为止。</p><h3 id="6-8-简单文件输入-输出"><a href="#6-8-简单文件输入-输出" class="headerlink" title="6.8 简单文件输入/输出"></a>6.8 简单文件输入/输出</h3><p>这里讨论的输入输出都是基于文本文件的输入输出，而非二进制文件。</p><h4 id="6-8-1-文本I-O和文本文件"><a href="#6-8-1-文本I-O和文本文件" class="headerlink" title="6.8.1 文本I/O和文本文件"></a>6.8.1 文本I/O和文本文件</h4><p>使用cin进行输入时，程序将输入视为一系列的字节，其中每个字节都被解释为字符编码。不管目标数据类型是什么，输入一开始都是字符数据——文本数据。cin负责将文本转换为其他类型。下面看示例：</p><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">38.5 19.2</span><br></pre></td></tr></table></figure><p>下面看看使用不同的数据类型的变量来存储时，cin是如何处理的：</p><ul><li><p>char</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">char</span> ch;</span><br><span class="line">cin &gt;&gt; ch;</span><br></pre></td></tr></table></figure><p>上面的代码使得cin看到第一个字符为3，并将其对应的字符编码存储在ch中。输入和目标变量都是字符，因此不需要任何转换。执行完上述步骤后，输入队列的下一个字符为8。</p></li><li><p>int</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> n;</span><br><span class="line">cin &gt;&gt; n;</span><br></pre></td></tr></table></figure><p>上面的代码使得cin将不断读取，直到遇到第一个非数字字符<code>.</code>，因此它将读取3和8，<code>.</code>成为输入队列中的下一个字符。cin通过计算发现，这两个字符对应数值38，因此将数值38的二进制编码赋值进变量n中。</p></li><li><p>double</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">double</span> x;</span><br><span class="line">cin &gt;&gt; x;</span><br></pre></td></tr></table></figure><p>上面的代码使得cin不断读取，知道遇到第一个不属于浮点数的字符。也就是说cin将读取3、8、<code>.</code>、5，空格成为输入队列的下一个字符。cin通过计算发现，这四个字符对应数值38.5，因此将38.5的二进制编码（浮点格式）复制到变量x中。</p></li><li><p>字符数组</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">char</span> word[<span class="number">50</span>];</span><br><span class="line">cin &gt;&gt; word;</span><br></pre></td></tr></table></figure><p>上面这段代码使得cin不断读取知道遇到空白字符，也就是说它将读取3、8、<code>.</code>、5，使得空格成为输入队列中的下一个字符。然后，cin将这4个字符的字符编码存储到word数组中，并在末尾加上一个<code>\0</code>。这里不需要进行任何的转换。</p></li><li><p>输入一行到字符数组</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">char</span> word[<span class="number">50</span>];</span><br><span class="line">cin.<span class="built_in">getline</span>(word, <span class="number">50</span>);</span><br></pre></td></tr></table></figure><p>这里假设输入行少于50个字符。cin将不断读取知道遇到换行符，并将换行符丢弃，将换行符之前读取的所有字符复制到word数组中并在结尾加上<code>\0</code>。输入队列中的下一个字符是下一行中的第一个字符。</p></li></ul><p>对于输出，将执行相反的转换。</p><h4 id="6-8-2-输出文件流ofstream"><a href="#6-8-2-输出文件流ofstream" class="headerlink" title="6.8.2 输出文件流ofstream"></a>6.8.2 输出文件流ofstream</h4><p>头文件fstream提供了文件输出流类ofstream。通过open方法打开文件后就可以像使用cout一样向文件中写入数据。用完后记得使用close方法关闭。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// outfile.cpp -- writing to a file</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;fstream&gt;</span>  <span class="comment">// step1: include header file</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</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">char</span> automobile[<span class="number">50</span>];</span><br><span class="line">    <span class="keyword">int</span> year;</span><br><span class="line">    <span class="keyword">double</span> a_price;</span><br><span class="line">    <span class="keyword">double</span> d_price;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// step2: declaration ofstream object</span></span><br><span class="line">    ofstream outFile;</span><br><span class="line">    <span class="comment">// step3: open file</span></span><br><span class="line">    outFile.<span class="built_in">open</span>(<span class="string">&quot;carinfo.txt&quot;</span>);</span><br><span class="line"></span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Enter the make and model of automobile: &quot;</span>;</span><br><span class="line">    cin.<span class="built_in">getline</span>(automobile, <span class="number">50</span>);</span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Enter the model year: &quot;</span>;</span><br><span class="line">    cin &gt;&gt; year;</span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Enter the original asking price: &quot;</span>;</span><br><span class="line">    cin &gt;&gt; a_price;</span><br><span class="line">    d_price = <span class="number">0.913</span> * a_price;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// display information on screen with cout</span></span><br><span class="line">    cout &lt;&lt; fixed;</span><br><span class="line">    cout.<span class="built_in">precision</span>(<span class="number">2</span>);</span><br><span class="line">    cout.<span class="built_in">setf</span>(ios_base::showpoint);</span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Make and Model: &quot;</span> &lt;&lt; automobile &lt;&lt; endl;</span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Year: &quot;</span> &lt;&lt; year &lt;&lt; endl;</span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Was asking $&quot;</span> &lt;&lt; a_price &lt;&lt; endl;</span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Now asking $&quot;</span> &lt;&lt; d_price &lt;&lt; endl;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// step4: now do exact same things using outFile instead of cout</span></span><br><span class="line">    outFile &lt;&lt; fixed;</span><br><span class="line">    outFile.<span class="built_in">precision</span>(<span class="number">2</span>);</span><br><span class="line">    outFile.<span class="built_in">setf</span>(ios_base::showpoint);</span><br><span class="line">    outFile &lt;&lt; <span class="string">&quot;Make and Model: &quot;</span> &lt;&lt; automobile &lt;&lt; endl;</span><br><span class="line">    outFile &lt;&lt; <span class="string">&quot;Year: &quot;</span> &lt;&lt; year &lt;&lt; endl;</span><br><span class="line">    outFile &lt;&lt; <span class="string">&quot;Was asking $&quot;</span> &lt;&lt; a_price &lt;&lt; endl;</span><br><span class="line">    outFile &lt;&lt; <span class="string">&quot;Now asking $&quot;</span> &lt;&lt; d_price &lt;&lt; endl;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// step5: close the stream</span></span><br><span class="line">    outFile.<span class="built_in">close</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>这里需要注意的是，打开已有的文件，以接受输出时，默认将其长度阶段为零，因此原来的内容将丢失。</p><h4 id="6-8-3-输入文件流ifstream"><a href="#6-8-3-输入文件流ifstream" class="headerlink" title="6.8.3 输入文件流ifstream"></a>6.8.3 输入文件流ifstream</h4><p>头文件fstream提供了文件输入流ifstream类，同样需要先声明一个ifstream对象，使用open函数打开文件，随后可以像使用cin一样使用它来从文件读入数据。</p><p>如果试图打开一个不存在的文件用于输入，将导致后面使用ifstream对象进行输入时失败。检查文件是否被成功打开的首先方法是使用is_open()。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;fstream&gt;</span>  <span class="comment">// step1: include header file &quot;fstream&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;cstdlib&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> SIZE = <span class="number">60</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line">    <span class="keyword">char</span> filename[SIZE];</span><br><span class="line">    ifstream inFile;  <span class="comment">// step2: declare a ifstream object</span></span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Enter name of data file: &quot;</span>;</span><br><span class="line">    cin.<span class="built_in">getline</span>(filename, SIZE);</span><br><span class="line">    inFile.<span class="built_in">open</span>(filename);  <span class="comment">// step3: open file</span></span><br><span class="line">    <span class="keyword">if</span> (!inFile.<span class="built_in">is_open</span>()) &#123;  <span class="comment">// step4: judge whether is opened successfully</span></span><br><span class="line">        cout &lt;&lt; <span class="string">&quot;Could not open the file &quot;</span> &lt;&lt; filename &lt;&lt; endl;</span><br><span class="line">        cout &lt;&lt; <span class="string">&quot;Program terminating.\n&quot;</span>;</span><br><span class="line">        <span class="built_in">exit</span>(EXIT_FAILURE);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">double</span> value;</span><br><span class="line">    <span class="keyword">double</span> sum = <span class="number">0.0</span>;</span><br><span class="line">    <span class="keyword">int</span> count = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// step5: read stream and test error</span></span><br><span class="line">    inFile &gt;&gt; value;</span><br><span class="line">    <span class="comment">// 没有任何错误发生，读成功时，good()返回true</span></span><br><span class="line">    <span class="keyword">while</span> (inFile.<span class="built_in">good</span>()) &#123;</span><br><span class="line">        ++count;</span><br><span class="line">        sum += value;</span><br><span class="line">        inFile &gt;&gt; value;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 判断是否到了文件尾</span></span><br><span class="line">    <span class="keyword">if</span> (inFile.<span class="built_in">eof</span>())</span><br><span class="line">        cout &lt;&lt; <span class="string">&quot;End of file reached.\n&quot;</span>;</span><br><span class="line">    <span class="comment">// 判断是否时输入错误</span></span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (inFile.<span class="built_in">fail</span>())</span><br><span class="line">        cout &lt;&lt; <span class="string">&quot;Input terminated by data mismatch.\n&quot;</span>;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        cout &lt;&lt; <span class="string">&quot;Input terminated for unknown reason.\n&quot;</span>;</span><br><span class="line">    <span class="keyword">if</span> (count == <span class="number">0</span>)</span><br><span class="line">        cout &lt;&lt; <span class="string">&quot;No data processed.\n&quot;</span>;</span><br><span class="line">    <span class="keyword">else</span> &#123;</span><br><span class="line">        cout &lt;&lt; <span class="string">&quot;Items read: &quot;</span> &lt;&lt; count &lt;&lt; endl;</span><br><span class="line">        cout &lt;&lt; <span class="string">&quot;Sum: &quot;</span> &lt;&lt; sum &lt;&lt; endl;</span><br><span class="line">        cout &lt;&lt; <span class="string">&quot;Average: &quot;</span> &lt;&lt; sum / count &lt;&lt; endl;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// step6: close stream</span></span><br><span class="line">    inFile.<span class="built_in">close</span>();  <span class="comment">// 关闭打开的文件</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>上述的good()循环可以简化为：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><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">while</span> (inFile &gt;&gt; value)</span><br><span class="line">&#123;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="第7章-函数——C-的编程模块"><a href="#第7章-函数——C-的编程模块" class="headerlink" title="第7章 函数——C++的编程模块"></a>第7章 函数——C++的编程模块</h2><h3 id="7-1-复习函数基础知识"><a href="#7-1-复习函数基础知识" class="headerlink" title="7.1 复习函数基础知识"></a>7.1 复习函数基础知识</h3><p>创建自己的函数时，必须自行处理3个方面：</p><ul><li>函数定义</li><li>函数原型</li><li>函数调用</li></ul><p>对于有返回值的函数，通常，函数将返回值复制到指定的CPU寄存器或内存单元中来将其返回。随后，调用程序将查看该内存单元。返回函数和调用函数必须就该内存单元中存储的数据的类型达成一致。函数原型将返回值类型告知调用程序，而函数定义命令被调用函数应返回什么类型的数据。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210331081144231.png" alt="image-20210331081144231"></p><h4 id="7-1-1-函数原型和函数定义"><a href="#7-1-1-函数原型和函数定义" class="headerlink" title="7.1.1 函数原型和函数定义"></a>7.1.1 函数原型和函数定义</h4><ol><li><p>为什么需要函数原型？</p><p>原型描述了函数到编译器的接口，它将函数返回值的类型以及参数的类型和数量告诉编译器。如果调用函数时提供的参数数量不对、类型不匹配（无法自动转换），原型使得编译器能捕获这些错误。当函数返回时，将值存储在CPU的寄存器或是内存中，由于原型提供了返回值类型，因此编译器知道应检索多少个字节以及如何解释它们。如果没有这些信息，编译器将只能进行猜测，而编译器是不会这样做的。</p><p>避免使用函数原型的唯一方法，在首次使用函数之前定义它，但这并不是总是可行的。</p></li><li><p>原型的语法</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="function">returnType <span class="title">functionName</span><span class="params">(param1Type, param2Type, ...)</span></span>;</span><br></pre></td></tr></table></figure><p>函数原型是一条语句，因此必须以分号结束。获得原型的最简单方式是复制函数定义的函数头，并添加分号。然而，函数原型并不要求提供变量名，可以包括变量名，也可以不包括。原型中的变量名相当于占位符，因此不必与函数定义中的变量名相同。</p></li><li><p>原型的功能</p><p>原型可以帮助编译器完成许多工作，也可以帮助程序员极大的降低程序出错的几率：</p><ul><li>编译器正确处理函数返回值。</li><li>编译器检查使用的参数数目是否正确。</li><li>编译器检查使用的参数类型是否正确。如果不正确，将转换为正确的类型（如果可能的话）。</li></ul><p>通常，原型自动将被传递的参数强制转换为期望的类型。自动类型转换并不能避免所有可能的错误。当较大的类型被自动转换为较小的类型时，有些编译器将发出警告，指出这可能会丢失数据。仅当有意义时，原型化才会导致类型转换。</p><p>在编译阶段进行的原型化被称为静态类型检查（static type checking）。可以看出，静态类型检查可以捕获许多在运行阶段非常难以捕获的错误。</p></li></ol><h3 id="7-2-函数参数和按值传递"><a href="#7-2-函数参数和按值传递" class="headerlink" title="7.2 函数参数和按值传递"></a>7.2 函数参数和按值传递</h3><p>函数按值传递，不同函数函数中的局部变量互不影响。</p><h3 id="7-3-函数和数组"><a href="#7-3-函数和数组" class="headerlink" title="7.3 函数和数组"></a>7.3 函数和数组</h3><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="function"><span class="keyword">int</span> <span class="title">sum_arr</span><span class="params">(<span class="keyword">int</span> arr[], <span class="keyword">int</span> n)</span></span>;</span><br></pre></td></tr></table></figure><p>上述的声明中，arr实际上并不是一个数组，而是一个指针。在C++中，当且仅当用于函数头或函数原型中，<code>int *arr</code>和<code>int arr[]</code>的含义是相同的，它们都意味着arr是一个指针。在其他上下文中，<code>int *arr</code>和<code>int arr[]</code>的含义并不相同。</p><p>将数组地址作为参数可以节省复制整个数组所需的时间和内存。如果数组很大，那么使用拷贝的系统开销将非常大。</p><h4 id="7-3-1-指针和const"><a href="#7-3-1-指针和const" class="headerlink" title="7.3.1 指针和const"></a>7.3.1 指针和const</h4><ul><li><p>让指针指向一个常量对象，防止使用该指针来修改所指向的值，简称指向const的指针。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> age = <span class="number">39</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> *pt = &amp;age;</span><br></pre></td></tr></table></figure><p>该声明指出，pt指向一个const int，因此不能使用pt来修改这个值。换句话说，*pt的值为const，不能被修改：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">*pt += <span class="number">1</span>;     <span class="comment">// INVALID</span></span><br><span class="line">cin &gt;&gt; *pt;   <span class="comment">// INVALID</span></span><br></pre></td></tr></table></figure><p>pt的声明并不意味着它指向的就是一个常量，而是只意味着对pt而言，这个值是常量。在这里，pt指向age，但age并不是常量，可以直接通过age变量修改age的值，但不能使用pt指针来修改它：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">*pt = <span class="number">20</span>;     <span class="comment">// INVALID</span></span><br><span class="line">age = <span class="number">20</span>;     <span class="comment">// VALID</span></span><br></pre></td></tr></table></figure><p>但你可以修改指针的指向，将其指向另一个变量，但是修改指向后仍不能使用指针修改变量的值。</p><p>四种情况：</p><ul><li><span style="color: green">将常规变量的地址赋给常规指针</span></li><li><span style="color: green">将常规变量的地址赋给指向const的指针</span></li><li><span style="color: green">将const变量的地址赋给指向const的指针</span></li><li><span style="color: red;">将const变量的地址赋给常规指针</span></li></ul><p>上述四种情况下，最后一种情况是不可行的。如果最后一种情况可行，那可以通过常规指针解引用来改变const变量的值，那这个const变量的const还有什么意义呢？（但可以使用强制类型转换类突破这种限制——const_cast）</p><p>数组元素如果为const类型，则禁止将数组赋给非const指针：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> arrays[<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 class="keyword">int</span> *pt = arrays;          <span class="comment">// INVALID</span></span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> *cpt = arrays;   <span class="comment">// Valid</span></span><br></pre></td></tr></table></figure><p><strong>尽可能使用const</strong></p><p>将指针参数声明为指向常量数据的指针有2条理由：</p><ul><li>这样可以避免由于无意间修改数据而导致的编程错误；</li><li>使用const使得函数能够处理const和非const实参，否则只能接受非const数据。</li></ul><p><strong>如果条件允许，则应将指针形参声明为指向const的指针。</strong></p></li><li><p>将指针本身声明为常量，防止改变指针的位置，简称const指针</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> sloth = <span class="number">3</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> *ps = &amp;sloth;</span><br><span class="line"><span class="keyword">int</span> * <span class="keyword">const</span> finger = &amp;sloth;</span><br></pre></td></tr></table></figure><p>ps为指向const的指针，不能用来修改sloth的值，只能重新指向别的变量。而finger不能修改指向，但可以使用<code>*finger</code>修改sloth的值。</p></li></ul><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210331151857454.png" alt="image-20210331151857454"></p><ul><li><p>如果愿意，还可以声明指向const对象的const指针：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">double</span> trouble = <span class="number">2.0E30</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="keyword">double</span> * <span class="keyword">const</span> stick = &amp;trouble;</span><br></pre></td></tr></table></figure></li></ul><h3 id="7-4-函数和二维数组"><a href="#7-4-函数和二维数组" class="headerlink" title="7.4 函数和二维数组"></a>7.4 函数和二维数组</h3><p>假设有如下代码：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> data[<span class="number">3</span>][<span class="number">4</span>] = &#123;&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>&#125;, &#123;<span class="number">9</span>, <span class="number">8</span>, <span class="number">7</span>, <span class="number">6</span>&#125;, &#123;<span class="number">2</span>, <span class="number">4</span>, <span class="number">6</span>, <span class="number">8</span>&#125;&#125;;</span><br><span class="line"><span class="keyword">int</span> total = <span class="built_in">sum</span>(data, <span class="number">3</span>);</span><br></pre></td></tr></table></figure><p>sum的原型中第一个参数应该如何声明？data为二维数组，该数组有3个元素，第一个元素本身是一个数组，有4个int值组成。因此data的类型是指向由4个int组成的数组的指针，因此正确的原型如下：</p><figure class="highlight c++"><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="function"><span class="keyword">int</span> <span class="title">sum</span><span class="params">(<span class="keyword">int</span> (*ar2)[<span class="number">4</span>], <span class="keyword">int</span> size)</span></span>;</span><br><span class="line">或</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">sum</span><span class="params">(<span class="keyword">int</span> ar2[][<span class="number">4</span>], <span class="keyword">int</span> size)</span></span>;</span><br></pre></td></tr></table></figure><p>其中的括号必不可少。在前面几章出现过指向数组的指针，在这里是同样的道理。这两种原型含义完全相同，第二种原型的可读性更强。而这两个原型都指出，ar2是指针，而不是数组。还需注意的是，指针类型指出，它指向由4个int组成的数组。因此，指针类型执行了列数，这就是没有将列数作为独立的函数参数进行传递的原因。</p><h3 id="7-5-函数和C风格字符串"><a href="#7-5-函数和C风格字符串" class="headerlink" title="7.5 函数和C风格字符串"></a>7.5 函数和C风格字符串</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><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">// strgback.cpp -- a function that returns a pointer to char</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">const</span> <span class="keyword">char</span> *<span class="title">buildstr</span><span class="params">(<span class="keyword">char</span> c, <span class="keyword">int</span> n)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">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="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line">    <span class="keyword">int</span> times;</span><br><span class="line">    <span class="keyword">char</span> ch;</span><br><span class="line"></span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Enter a character: &quot;</span>;</span><br><span class="line">    cin &gt;&gt; ch;</span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Enter an integer: &quot;</span>;</span><br><span class="line">    cin &gt;&gt; times;</span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">char</span> *ps = <span class="built_in">buildstr</span>(ch, times);</span><br><span class="line">    cout &lt;&lt; ps &lt;&lt; endl;</span><br><span class="line">    <span class="keyword">delete</span>[] ps;</span><br><span class="line">    ps = <span class="built_in">buildstr</span>(<span class="string">&#x27;+&#x27;</span>, <span class="number">20</span>);</span><br><span class="line">    cout &lt;&lt; ps &lt;&lt; <span class="string">&quot;-DONE-&quot;</span> &lt;&lt; ps &lt;&lt; endl;</span><br><span class="line">    <span class="keyword">delete</span>[] ps;</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><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">const</span> <span class="keyword">char</span> *<span class="title">buildstr</span><span class="params">(<span class="keyword">char</span> c, <span class="keyword">int</span> n)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">char</span> *pstr = <span class="keyword">new</span> <span class="keyword">char</span>[n + <span class="number">1</span>];</span><br><span class="line">    pstr[n] = <span class="string">&#x27;\0&#x27;</span>;</span><br><span class="line">    <span class="keyword">while</span> (n-- &gt; <span class="number">0</span>)</span><br><span class="line">        pstr[n] = c;</span><br><span class="line">    <span class="keyword">return</span> pstr;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里省略所有的const也是可以的。加const是为了防止修改字符串的值。</p><h3 id="7-6-递归"><a href="#7-6-递归" class="headerlink" title="7.6 递归"></a>7.6 递归</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ruler.cpp -- using recursion to subdivde a ruler</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> Len = <span class="number">66</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> Divs = <span class="number">6</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">subdivide</span><span class="params">(<span class="keyword">char</span> ar[], <span class="keyword">int</span> low, <span class="keyword">int</span> high, <span class="keyword">int</span> level)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">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="keyword">char</span> ruler[Len];</span><br><span class="line">    <span class="keyword">int</span> i;</span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">1</span>; i &lt; Len - <span class="number">2</span>; i++)</span><br><span class="line">        ruler[i] = <span class="string">&#x27; &#x27;</span>;</span><br><span class="line">    ruler[Len - <span class="number">1</span>] = <span class="string">&#x27;\0&#x27;</span>;</span><br><span class="line">    <span class="keyword">int</span> max = Len - <span class="number">2</span>;</span><br><span class="line">    <span class="keyword">int</span> min = <span class="number">0</span>;</span><br><span class="line">    ruler[min] = ruler[max] = <span class="string">&#x27;|&#x27;</span>;</span><br><span class="line">    std::cout &lt;&lt; ruler &lt;&lt; std::endl;</span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt;= Divs; i++)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="built_in">subdivide</span>(ruler, min, max, i);</span><br><span class="line">        std::cout &lt;&lt; ruler &lt;&lt; std::endl;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">1</span>; j &lt; Len - <span class="number">2</span>; j++)</span><br><span class="line">            ruler[j] = <span class="string">&#x27; &#x27;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">subdivide</span><span class="params">(<span class="keyword">char</span> ar[], <span class="keyword">int</span> low, <span class="keyword">int</span> high, <span class="keyword">int</span> level)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (level == <span class="number">0</span>) <span class="comment">// 递归边界</span></span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    <span class="keyword">int</span> mid = (low + high) / <span class="number">2</span>;</span><br><span class="line">    ar[mid] = <span class="string">&#x27;|&#x27;</span>;</span><br><span class="line">    <span class="built_in">subdivide</span>(ar, low, mid, level - <span class="number">1</span>);</span><br><span class="line">    <span class="built_in">subdivide</span>(ar, mid, high, level - <span class="number">1</span>);</span><br><span class="line">&#125;</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><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><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">| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |</span><br><span class="line">|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||</span><br></pre></td></tr></table></figure><h3 id="7-7-函数指针"><a href="#7-7-函数指针" class="headerlink" title="7.7 函数指针"></a>7.7 函数指针</h3><p>与数据项相似，函数也有地址。函数的地址是存储在其机器语言代码的内存的开始地址。</p><h4 id="7-10-1-函数指针的基础知识"><a href="#7-10-1-函数指针的基础知识" class="headerlink" title="7.10.1 函数指针的基础知识"></a>7.10.1 函数指针的基础知识</h4><ul><li><p>获取函数的地址</p><p>获取函数的地址很简单：只要使用函数名即可。也就是说，如果think()是一个函数，则think就是该函数的地址。要将函数作为参数进行传递，必须传递函数名。</p></li><li><p>声明函数指针</p><p>声明指向某种数据类型的指针时，必须指定指针指向的类型。同样，声明指向函数的指针时，也必须指定指针指向的函数类型。这意味着声明应指定函数的返回类型以及函数的特征标（参数列表）。也就是说，声明应像函数原型那样指出有关函数的信息。</p><blockquote><p>通常，要声明指向特定类型的函数的指针，可以先编写这种函数的原型，然后用<code>(*pf)</code>替换函数名。这样，pf就是这类函数的指针。</p></blockquote><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// a function declaration</span></span><br><span class="line"><span class="function"><span class="keyword">double</span> <span class="title">pam</span><span class="params">(<span class="keyword">int</span>)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// a pointer point to function pam</span></span><br><span class="line"><span class="built_in"><span class="keyword">double</span></span> (*pf)(<span class="keyword">int</span>) = pam;</span><br></pre></td></tr></table></figure><p>函数的特征标和返回类型必须与函数指针一致。如果不相同，编译器将拒绝这种赋值。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">double</span> <span class="title">ned</span><span class="params">(<span class="keyword">double</span>)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">ted</span><span class="params">(<span class="keyword">int</span>)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in"><span class="keyword">double</span></span> (*pf)(<span class="keyword">int</span>);</span><br><span class="line"></span><br><span class="line">pf = ned;          <span class="comment">// INVALID  -- mismatched signature</span></span><br><span class="line">pf = ted;          <span class="comment">// INVALID  -- mismatched return types</span></span><br></pre></td></tr></table></figure></li><li><p>使用指针来调用函数</p><p>将函数指针看作函数名即可。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">double</span> <span class="title">pam</span><span class="params">(<span class="keyword">int</span>)</span></span>;</span><br><span class="line"><span class="built_in"><span class="keyword">double</span></span> (*pf)(<span class="keyword">int</span>);</span><br><span class="line">pf = pam;</span><br><span class="line"><span class="keyword">double</span> x = <span class="built_in">pam</span>(<span class="number">4</span>);</span><br><span class="line"><span class="keyword">double</span> y = *(pf)(<span class="number">5</span>)    <span class="comment">// calling by function pointer</span></span><br></pre></td></tr></table></figure><p>实际上，C++也允许像使用函数名那样使用pf:</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="keyword">double</span> y = <span class="built_in">pf</span>(<span class="number">5</span>);</span><br></pre></td></tr></table></figure><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210331171355702.png" alt="image-20210331171355702"></p></li></ul><h4 id="7-10-2-深入探讨函数指针"><a href="#7-10-2-深入探讨函数指针" class="headerlink" title="7.10.2 深入探讨函数指针"></a>7.10.2 深入探讨函数指针</h4><p>先看三个函数原型：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">const</span> <span class="keyword">double</span> *<span class="title">f1</span><span class="params">(<span class="keyword">const</span> <span class="keyword">double</span> ar[], <span class="keyword">int</span> n)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">const</span> <span class="keyword">double</span> *<span class="title">f2</span><span class="params">(<span class="keyword">const</span> <span class="keyword">double</span> [], <span class="keyword">int</span>)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">const</span> <span class="keyword">double</span> *<span class="title">f3</span><span class="params">(<span class="keyword">const</span> <span class="keyword">double</span> *, <span class="keyword">int</span>)</span></span>;</span><br></pre></td></tr></table></figure><p>实际上，上面三个原型是一样的，特征表看似不同，实际上相同。接下来声明一个指针，它可指向这三个函数之一：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="keyword">double</span> *(*p1)(<span class="keyword">const</span> <span class="keyword">double</span> *, <span class="keyword">int</span>);</span><br></pre></td></tr></table></figure><p>可在声明的同时进行初始化：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="keyword">double</span> *(*p1)(<span class="keyword">const</span> <span class="keyword">double</span> *, <span class="keyword">int</span>) = f1;</span><br><span class="line"><span class="keyword">auto</span> p2 = f2;                                      <span class="comment">// C++11 automatic type deduction</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// using </span></span><br><span class="line">cout &lt;&lt; (*p1)(av, <span class="number">3</span>) &lt;&lt; <span class="string">&quot;: &quot;</span> &lt;&lt; *(*p1)(av, <span class="number">3</span>) &lt;&lt; endl;</span><br><span class="line">cout &lt;&lt; <span class="built_in">p2</span>(av, <span class="number">3</span>) &lt;&lt; <span class="string">&quot;: &quot;</span> &lt;&lt; *<span class="built_in">p2</span>(av, <span class="number">3</span>) &lt;&lt; endl;</span><br></pre></td></tr></table></figure><p>函数返回<code>const double *</code>为指针类型，因此使用解引用符号取得值。</p><p><span style="color: red">函数指针数组</span></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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">const</span> <span class="keyword">double</span> *(*pa[<span class="number">3</span>])(<span class="keyword">const</span> <span class="keyword">double</span> *, <span class="keyword">int</span>) = &#123;f1, f2, f3&#125;;</span><br><span class="line"><span class="keyword">auto</span> pb = pa;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="keyword">double</span> *px = pa[<span class="number">0</span>](av, <span class="number">3</span>);</span><br><span class="line"><span class="keyword">const</span> <span class="keyword">double</span> *py = (*pb[<span class="number">1</span>])(av, <span class="number">3</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">double</span> x = *pa[<span class="number">0</span>](av, <span class="number">3</span>);</span><br><span class="line"><span class="keyword">double</span> y = *(*pb[<span class="number">1</span>])(av, <span class="number">3</span>);</span><br></pre></td></tr></table></figure><p><span style="color: red">指向函数指针数组的指针</span></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">auto</span> pc = &amp;pa;  <span class="comment">// C++11</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="keyword">double</span> *(*(*pd)[<span class="number">3</span>])(<span class="keyword">const</span> <span class="keyword">double</span> *, <span class="keyword">int</span>) = &amp;pa;</span><br></pre></td></tr></table></figure><p>一个例子：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">const</span> <span class="keyword">double</span> *<span class="title">f1</span><span class="params">(<span class="keyword">const</span> <span class="keyword">double</span> ar[], <span class="keyword">int</span> n)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">const</span> <span class="keyword">double</span> *<span class="title">f2</span><span class="params">(<span class="keyword">const</span> <span class="keyword">double</span> [], <span class="keyword">int</span>)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">const</span> <span class="keyword">double</span> *<span class="title">f3</span><span class="params">(<span class="keyword">const</span> <span class="keyword">double</span> *, <span class="keyword">int</span>)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">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="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line">    <span class="keyword">double</span> av[<span class="number">3</span>] = &#123;<span class="number">1112.3</span>, <span class="number">1542.6</span>, <span class="number">2227.9</span>&#125;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">double</span> *(*p1)(<span class="keyword">const</span> <span class="keyword">double</span> *, <span class="keyword">int</span>) = f1;</span><br><span class="line">    <span class="keyword">auto</span> p2 = f2;</span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Using prointers to functions:\n&quot;</span>;</span><br><span class="line">    cout &lt;&lt; <span class="string">&quot; Address  Value\n&quot;</span>;</span><br><span class="line">    cout &lt;&lt; (*p1)(av, <span class="number">3</span>) &lt;&lt; <span class="string">&quot;: &quot;</span> &lt;&lt; *(*p1)(av, <span class="number">3</span>) &lt;&lt; endl;</span><br><span class="line">    cout &lt;&lt; <span class="built_in">p2</span>(av, <span class="number">3</span>) &lt;&lt; <span class="string">&quot;: &quot;</span> &lt;&lt; *<span class="built_in">p2</span>(av, <span class="number">3</span>) &lt;&lt; endl;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 函数指针数组</span></span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">double</span> *(*pa[<span class="number">3</span>])(<span class="keyword">const</span> <span class="keyword">double</span> *, <span class="keyword">int</span>) = &#123;f1, f2, f3&#125;;</span><br><span class="line">    <span class="keyword">auto</span> pb = pa;</span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;\nUsing an array of pointers to functions:\n&quot;</span>;</span><br><span class="line">    cout &lt;&lt; <span class="string">&quot; Address  Value\n&quot;</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">3</span>; i++)</span><br><span class="line">        cout &lt;&lt; pa[i](av, <span class="number">3</span>) &lt;&lt; <span class="string">&quot;: &quot;</span> &lt;&lt; *pa[i](av, <span class="number">3</span>) &lt;&lt; endl;</span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;\nUsing a pointer to a pointer to a function:\n&quot;</span>;</span><br><span class="line">    cout &lt;&lt; <span class="string">&quot; Address  Value\n&quot;</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">3</span>; i++)</span><br><span class="line">        cout &lt;&lt; pb[i](av, <span class="number">3</span>) &lt;&lt; <span class="string">&quot;: &quot;</span> &lt;&lt; *pb[i](av, <span class="number">3</span>) &lt;&lt; endl;</span><br><span class="line">    </span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;\nUsing pointers to an array of pointers:\n&quot;</span>;</span><br><span class="line">    cout &lt;&lt; <span class="string">&quot; Address  Value\n&quot;</span>;</span><br><span class="line">    <span class="comment">// 指向函数指针数组的指针</span></span><br><span class="line">    <span class="keyword">auto</span> pc = &amp;pa;</span><br><span class="line">    cout &lt;&lt; (*pc)[<span class="number">0</span>](av, <span class="number">3</span>) &lt;&lt; <span class="string">&quot;: &quot;</span> &lt;&lt; *(*pc)[<span class="number">0</span>](av, <span class="number">3</span>) &lt;&lt; endl;</span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">double</span> *(*(*pd)[<span class="number">3</span>])(<span class="keyword">const</span> <span class="keyword">double</span> *, <span class="keyword">int</span>) = &amp;pa;</span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">double</span> *pdb = (*pd)[<span class="number">1</span>](av, <span class="number">3</span>);</span><br><span class="line">    cout &lt;&lt; pdb &lt;&lt; <span class="string">&quot;: &quot;</span> &lt;&lt; *pdb &lt;&lt; endl;</span><br><span class="line">    cout &lt;&lt; (*(*pd)[<span class="number">2</span>])(av, <span class="number">3</span>) &lt;&lt; <span class="string">&quot;: &quot;</span> &lt;&lt; *((*pd)[<span class="number">2</span>])(av, <span class="number">3</span>) &lt;&lt; endl;</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><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">const</span> <span class="keyword">double</span> *<span class="title">f1</span><span class="params">(<span class="keyword">const</span> <span class="keyword">double</span> *arr, <span class="keyword">int</span> n)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> arr;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">const</span> <span class="keyword">double</span> *<span class="title">f2</span><span class="params">(<span class="keyword">const</span> <span class="keyword">double</span> ar[], <span class="keyword">int</span> n)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> ar + <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="keyword">const</span> <span class="keyword">double</span> *<span class="title">f3</span><span class="params">(<span class="keyword">const</span> <span class="keyword">double</span> ar[], <span class="keyword">int</span> n)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> ar + <span class="number">2</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="第8章-函数探幽"><a href="#第8章-函数探幽" class="headerlink" title="第8章 函数探幽"></a>第8章 函数探幽</h2><p>本章重点内容：</p><ul><li>内联函数</li><li>引用</li><li>函数的默认值参数</li><li>函数重载</li><li>函数模板</li><li>函数模板具体化</li></ul><h3 id="8-1-C-内联函数"><a href="#8-1-C-内联函数" class="headerlink" title="8.1 C++内联函数"></a>8.1 C++内联函数</h3><p>内联函数是C++为提高程序运行速度所做的一项改进。编译过程的最终产品是可执行程序——右一组机器语言指令组成。运行程序时，操作系统将这些指令载入到计算机内存中，因此每条指令都有特定的内存地址。计算机随后将逐步执行这些指令。有时，碰到循环或分支语句时，将跳过一些指令，想前或向后跳到特定的地址。常规函数调用也使程序跳到另一个地址——函数的地址，并在函数结束时返回。</p><p>执行到函数调用指令时，程序将在函数调用后立即存储该指令的内存地址，并将函数参数复制到堆栈，跳到标记函数起点的内存单元，执行函数代码，然后跳回到地址被保存的指令处。来回跳跃并记录跳跃位置意味着以前使用函数时，需要一定的开销。</p><p>C++内联函数提供了另一种选择，以空间换时间，内联函数不进行上述的跳跃过程，编译器将使用相应的函数代码替换函数调用，因此内联函数的运行速度比常规函数快，但代价是占用更多内存。如果程序在10个不同的地方调用同一个内联函数，则该程序将包含该函数代码的10个副本。</p><p>应该有选择地使用内联函数，如果执行函数代码的时间比处理函数调用机制的时间长，则节省的时间将只占整个过程的很小一部分。如果代码执行的时间很短，则内联调用就可以节省非内联调用使用的大部分时间。另一方面，由于这个过程相当快，因此尽管节省了该过程的大部分时间，但节省的时间绝对值并不大，除非该函数经常被调用。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210402195829958.png" alt="image-20210402195829958"></p><p>要使用这项特性，必须采取下述措施之一：</p><ul><li>在函数声明前加上关键字inline</li><li>在函数定义前加上关键字inline</li></ul><p>通常的做法是省略原型，将整个定义放在本应提供原型的地方。</p><p><strong>内联函数不能为递归函数</strong>。程序员请求将函数作为内联函数时，编译器不一定会满足这种要求。它可能认为该函数过大或注意到函数调用了自己，因此不将它作为内联函数。有些编译器没有启用或实现这种特性。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210402200839919.png" alt="image-20210402200839919"></p><h3 id="8-2-引用变量"><a href="#8-2-引用变量" class="headerlink" title="8.2 引用变量"></a>8.2 引用变量</h3><p>引用是已定义的变量的<strong>别名</strong>。（引用的实现方式为const指针，且隐含着解引用）。引用变量的主要用途是用作函数的形参。通过将引用变量作为参数，函数将使用原始数据，而不是副本。</p><p><strong>引用必须在其声明时初始化</strong>，而不像指针可以先声明后初始化。此后，每次对引用变量使用赋值运算符，仅仅是在赋值，而不是将引用重新指向一个新的变量。</p><p>使用引用作为形参时，由于参数为传进来的实参的引用，因此修改该参数的值，将修改实参的值。为了避免这种情况，可以使用const修饰符。如此一来，当编译器发现修改引用的值时，编译器将报错。</p><p>当参数是基本类型时，使用值传递；如果为结构体变量或类对象时，使用引用传递。</p><h4 id="8-2-1-临时变量、引用参数和const"><a href="#8-2-1-临时变量、引用参数和const" class="headerlink" title="8.2.1 临时变量、引用参数和const"></a>8.2.1 临时变量、引用参数和const</h4><p>如果实参与引用参数不匹配，C++将生成<strong>临时变量</strong>。当前，仅当参数为const引用时，C++才允许这样做，但以前不这样。</p><p>如果引用参数是const，则编译器将在下面两种情况下生成临时变量：</p><ul><li>实参的类型正确，但不是左值；</li><li>实参的类型不正确，但可以转换为正确的类型</li></ul><p>左值：可被引用的数据对象，例如变量、数组元素、结构成员、引用和解引用的指针等。</p><p>非左值：字面常量、包含多项的表达式。</p><p>常规变量和const变量都可视为左值，因为可以通过地址访问它们。但常规变量属于可修改的左值，而const变量属于不可修改的左值。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">double</span> <span class="title">refcube</span><span class="params">(<span class="keyword">const</span> <span class="keyword">double</span> &amp;ra)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> ra * ra * ra;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">double</span> side = <span class="number">3.0</span>;</span><br><span class="line"><span class="keyword">double</span> *pd = &amp;side;</span><br><span class="line"><span class="keyword">double</span> &amp;rd = side;</span><br><span class="line"><span class="keyword">long</span> edge = <span class="number">5L</span>;</span><br><span class="line"><span class="keyword">double</span> lens[<span class="number">4</span>] = &#123;<span class="number">2.0</span>, <span class="number">5.0</span>, <span class="number">10.0</span>, <span class="number">12.0</span>&#125;;</span><br><span class="line"><span class="keyword">double</span> c1 = <span class="built_in">refcube</span>(side);         <span class="comment">// ra is sice</span></span><br><span class="line"><span class="keyword">double</span> c2 = <span class="built_in">refcube</span>(lens[<span class="number">2</span>]);      <span class="comment">// ra is lens[2]</span></span><br><span class="line"><span class="keyword">double</span> c3 = <span class="built_in">refcube</span>(rd);           <span class="comment">// ra is rd is side</span></span><br><span class="line"><span class="keyword">double</span> c4 = <span class="built_in">refcube</span>(*pd);          <span class="comment">// ra is *pd is side</span></span><br><span class="line"><span class="keyword">double</span> c5 = <span class="built_in">refcube</span>(edge);         <span class="comment">// ra is temporary variable, type is not match, but can be converted</span></span><br><span class="line"><span class="keyword">double</span> c6 = <span class="built_in">refcube</span>(<span class="number">7.0</span>);          <span class="comment">// ra is temporary variable, type is match, but is not left value</span></span><br><span class="line"><span class="keyword">double</span> c7 = <span class="built_in">refcube</span>(side + <span class="number">10.0</span>);  <span class="comment">// ra is temporary variable, type is match, but is not left value</span></span><br></pre></td></tr></table></figure><p>edge虽然是变量，是左值，但是一个long类型，类型不对；7.0和 side + 10.0类型都对，但不是左值，没有名称。在这些情况下，编译器都将生成一个临时匿名变量，并让ra指向该临时变量。这些临时变量只在函数调用期间存在，此后编译器变可以随意将其删除。</p><p>现在来看refcube()函数，该函数的目的只是使用传递的值，而不是修改它们，因此临时变量不会造成任何不利的影响，反而会使函数在可处理的参数种类方面更通用。因此，如果将引用声明为const类型，C++将在必要时产生临时变量。实际上，对于形参为const引用的C++参数，如果实参类型不匹配，则其行为类似于按值传递，为确保原始数据不被改变，将使用临时变量来存储值。</p><p><strong>应尽可能使用const</strong></p><ul><li>使用const可以避免无意中修改数据的编程错误；</li><li>使用const使函数能够处理const和非const实参，否则只能接受非const数据；</li><li>使用const引用使函数能够正确生成并使用临时变量。</li></ul><h4 id="8-2-2-右值引用"><a href="#8-2-2-右值引用" class="headerlink" title="8.2.2 右值引用"></a>8.2.2 右值引用</h4><p>C++新增了另一种引用——右值引用（rvalue refrence）。这种引用可以指向右值，使用<code>&amp;&amp;</code>声明。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">double</span> &amp;&amp;rref = std::<span class="built_in">sqrt</span>(<span class="number">36.00</span>);</span><br><span class="line"><span class="keyword">double</span> j = <span class="number">15.0</span>;</span><br><span class="line"><span class="keyword">double</span> &amp;&amp;jref = <span class="number">2.0</span> * j + <span class="number">18.5</span>;</span><br><span class="line">std::cout &lt;&lt; rref &lt;&lt; std::endl;</span><br><span class="line">std::cout &lt;&lt; jref &lt;&lt; std::endl;</span><br></pre></td></tr></table></figure><h4 id="8-2-3-返回引用"><a href="#8-2-3-返回引用" class="headerlink" title="8.2.3 返回引用"></a>8.2.3 返回引用</h4><p>返回引用的函数实际上是被引用的变量的别名。</p><p>返回引用时最重要的一点，<strong>应避免返回函数终止时不再存在的内存单元引用</strong>。应该避免编写下面的代码：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">const</span> free_throws &amp;<span class="title">clone2</span><span class="params">(free_throws &amp;ft)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    free_throws newguy;</span><br><span class="line">    newguy = ft;</span><br><span class="line">    <span class="keyword">return</span> newguy;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>该函数返回一个指向临时变量的引用，函数运行完毕后它将不复存在。为避免这种问题，最简单的方法是，返回一个作为参数传递给函数的引用。作为参数传递的引用将指向调用函数使用的数据，因此返回的引用也指向这些数据。另一种方法是使用new来分配新的存储空间。</p><p>返回const引用使得无法对函数调用语句进行赋值：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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="function">free_throws &amp;<span class="title">accumulate</span><span class="params">(free_throws &amp;target, <span class="keyword">const</span> free_throws &amp;source)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    <span class="keyword">return</span> target;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">accumulate</span>(dup, four) = three;  <span class="comment">// dup = three;</span></span><br></pre></td></tr></table></figure><p>上面这条个函数原型指示返回值是一个free_throws的引用，因此可以对其赋值，因为返回的为左值；</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="function"><span class="keyword">const</span> free_throws &amp;<span class="title">accumulate</span><span class="params">(free_throws &amp;target, <span class="keyword">const</span> free_throws &amp;source)</span></span></span><br></pre></td></tr></table></figure><p>上面这条语句便无法对函数调用进行赋值，因为返回值不再是左值，而是const修饰的引用，无法修改值，为右值。</p><h4 id="8-2-4-对象、继承和引用"><a href="#8-2-4-对象、继承和引用" class="headerlink" title="8.2.4 对象、继承和引用"></a>8.2.4 对象、继承和引用</h4><p>基类引用可以指向派生类对象，而无需进行强制类型转换，常用于函数参数。举个简单的例子，ofstream类继承ostream类，因此指向ostream类的引用也可以指向ofstream类。</p><h4 id="8-2-5-何时使用引用参数"><a href="#8-2-5-何时使用引用参数" class="headerlink" title="8.2.5 何时使用引用参数"></a>8.2.5 何时使用引用参数</h4><p>使用引用参数的两个原因：</p><ul><li>程序员能够修改调用函数中的数据对象</li><li>通过传递引用而不是整个对象，可以提高程序的运行速度</li></ul><p>指导原则：</p><ol><li>对于使用传递的值而不作修改的函数<ul><li>如果数据对象很小，如内置数据类型或小型结构，则按值传递</li><li>如果数据对象是数组，则使用指针，因为这是唯一的选择，并将指针声明为指向const的指针</li><li>如果数据对象是较大的结构，则使用指向const的指针或const引用</li><li>如果数据对象是对象，则使用const引用</li></ul></li><li>对于修改调用函数中数据的函数：<ul><li>如果数据对象是内置数据类型，则使用指针</li><li>如果数据对象是数组，则使用指针</li><li>如果数据都对象是结构，则使用引用或指针</li><li>如果数据对象是类对象，则使用引用</li></ul></li></ol><h3 id="8-3-默认参数"><a href="#8-3-默认参数" class="headerlink" title="8.3 默认参数"></a>8.3 默认参数</h3><p>默认参数通过函数原型设置并告知程序。对于带参数列表的函数，必须从右向左添加默认值，也就是说，要为某个参数设置默认值，则必须为它右边的所有参数提供默认值。</p><h3 id="8-4-函数重载"><a href="#8-4-函数重载" class="headerlink" title="8.4 函数重载"></a>8.4 函数重载</h3><p>可以通过函数重载来设计一系列函数——它们完成相同的工作，但使用不同的<strong>参数列表</strong>。函数重载的关键是参数列表——也称为特征标。如果两个函数的参数数目和类型相同，同时参数的排列顺序也相同，则它们的特征标相同，而变量名和返回值类型是无关紧要的。</p><p>编译器在检查函数特征标时，将把类型和引用本身视为同一特征标。</p><h4 id="8-4-1-重载引用参数"><a href="#8-4-1-重载引用参数" class="headerlink" title="8.4.1 重载引用参数"></a>8.4.1 重载引用参数</h4><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">stove</span><span class="params">(<span class="keyword">double</span> &amp;r1)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">stove</span><span class="params">(<span class="keyword">const</span> <span class="keyword">double</span> &amp;r2)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">stove</span><span class="params">(<span class="keyword">double</span> &amp;&amp;r3)</span></span>;</span><br></pre></td></tr></table></figure><p>上面有3个函数原型，左值引用参数<code>r1</code>与可修改的左值参数匹配；const左值引用参数<code>r2</code>可与修改的左值、const左值参数和右值参数匹配；右值引用参数<code>r3</code>与右值匹配。如果重载使用这三种参数的函数，结果将如何？答案是将调用最匹配的版本：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">double</span> x = <span class="number">55.5</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="keyword">double</span> y = <span class="number">32.0</span>;</span><br><span class="line"><span class="built_in">stove</span>(x);           <span class="comment">// call stove(double &amp;)</span></span><br><span class="line"><span class="built_in">stove</span>(y);           <span class="comment">// call stove(const double &amp;)</span></span><br><span class="line"><span class="built_in">stove</span>(x + y);       <span class="comment">// call stove(double &amp;&amp;)</span></span><br></pre></td></tr></table></figure><p>如果没有定义<code>stove(double &amp;&amp;)</code>，<code>stove(x + y)</code>将调用<code>stove(const double &amp;)</code>。</p><h4 id="8-4-2-何时使用函数重载"><a href="#8-4-2-何时使用函数重载" class="headerlink" title="8.4.2 何时使用函数重载"></a>8.4.2 何时使用函数重载</h4><p>仅当函数基本上执行相同的任务，但使用不同形式的数据时，才应采用函数重载。有时，默认参数可以达到相同的目的，且能少些一个函数。</p><h4 id="8-4-3-名称修饰"><a href="#8-4-3-名称修饰" class="headerlink" title="8.4.3 名称修饰"></a>8.4.3 名称修饰</h4><p>编译器根据函数原型中指定的形参类型对每个函数名进行加密，如在64位Windows上，且编译器架构为x86_64，使用<code>nm</code>命令查看目标文件，可以看到函数经编译器进行名称修饰后的结果：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">unsigned</span> <span class="keyword">long</span> <span class="title">left</span><span class="params">(<span class="keyword">unsigned</span> <span class="keyword">long</span> num, <span class="keyword">unsigned</span> ct)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">char</span> *<span class="title">left</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *str, <span class="keyword">int</span> n = <span class="number">1</span>)</span></span>;</span><br></pre></td></tr></table></figure><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210406205619342.png" alt="image-20210406205619342"></p><h3 id="8-5-函数模板"><a href="#8-5-函数模板" class="headerlink" title="8.5 函数模板"></a>8.5 函数模板</h3><p>函数模板是通用的函数描述，也就是说，它们使用泛型来定义函数，其中的泛型可用具体的类型替换。通过将类型作为参数传递给模板，可使编译器生成该类型的函数。</p><p>模板并不创建任何函数，而是告诉编译器如何定义函数。当指定类型时，编译器将按模板创建对应类型的函数，这被称为模板实例化。</p><p>函数模板不能缩短可执行程序，最终编译器会根据模板创建出多个不同类型的函数定义，而最终的代码不包含任何模板。</p><h4 id="8-5-1-重载的模板"><a href="#8-5-1-重载的模板" class="headerlink" title="8.5.1 重载的模板"></a>8.5.1 重载的模板</h4><p>需要多个对不同类型使用同一种算法的函数时，可使用模板。然而，并非所有的类型都使用相同的算法。为满足这种需求，可以像重载常规函数定义那样重载模板定义。和常规重载一样，被重载的模板的函数特征标必须不同。</p><h4 id="8-5-2-模板的局限性"><a href="#8-5-2-模板的局限性" class="headerlink" title="8.5.2 模板的局限性"></a>8.5.2 模板的局限性</h4><p>模板应配合运算符重载，才可将通用的操作用于不同的类型。或者，为特定类型提供具体化的模板定义。</p><h4 id="8-5-3-显示具体化"><a href="#8-5-3-显示具体化" class="headerlink" title="8.5.3 显示具体化"></a>8.5.3 显示具体化</h4><p>可以提供一个具体化函数定义——显示具体化(explicit specialization)，其中包含所需的代码。当编译器找到与函数调用匹配的具体化定义时，将使用该定义，而不再寻找模板。</p><ul><li>对于给定的函数名，可以有非模板函数、模板函数和显示具体化模板函数以及它们的重载版本。</li><li>显示具体化的原型和定义应以<code>template&lt;&gt;</code>打头，并通过名称来指出类型。</li><li>具体化优先于常规模板，而非模板函数优先于具体化和常规模板。</li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><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 class="function"><span class="keyword">void</span> <span class="title">Swap</span><span class="params">(job &amp;, job&amp;)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 模板函数</span></span><br><span class="line"><span class="keyword">template</span> &lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Swap</span><span class="params">(T &amp;, T &amp;)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 显示具体化</span></span><br><span class="line"><span class="keyword">template</span> &lt;&gt; <span class="keyword">void</span> Swap&lt;job&gt;(job &amp;, job &amp;);  <span class="comment">// &lt;job&gt;可选，因为函数的参数类型表明，这是job的一个具体化</span></span><br><span class="line"><span class="keyword">template</span> &lt;&gt; <span class="function"><span class="keyword">void</span> <span class="title">Swap</span><span class="params">(job &amp;, job &amp;)</span></span>;       <span class="comment">// 因此可以这样写</span></span><br></pre></td></tr></table></figure><h4 id="8-5-4-实例化和具体化"><a href="#8-5-4-实例化和具体化" class="headerlink" title="8.5.4 实例化和具体化"></a>8.5.4 实例化和具体化</h4><p>在代码中包含函数模板本身并不会生成函数定义，它只是一个用于生成函数定义的方案。编译器使用模板为特定类型生成函数定义时，得到的使模板示例（instantiation）。模板并非函数定义，但使用int的模板实例是函数定义。</p><ol><li><p>实例化</p><ul><li><p>隐式实例化</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> a = <span class="number">10</span>, b = <span class="number">20</span>;</span><br><span class="line"><span class="built_in">Swap</span>(a, b);</span><br></pre></td></tr></table></figure></li><li><p>显示实例化</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Swap&lt;<span class="keyword">int</span>&gt;(a, b);</span><br><span class="line"><span class="keyword">template</span> <span class="keyword">void</span> Swap&lt;<span class="keyword">int</span>&gt;(<span class="keyword">int</span>, <span class="keyword">int</span>);</span><br></pre></td></tr></table></figure><p>该声明的意思是，使用Swap模板生成int类型的函数定义。</p></li></ul></li><li><p>具体化</p><p>显示具体化使用下面两个等价的声明之一：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span> &lt;&gt; <span class="keyword">void</span> Swap&lt;<span class="keyword">int</span>&gt;(<span class="keyword">int</span> &amp;, <span class="keyword">int</span> &amp;);    <span class="comment">// explicit specialization</span></span><br><span class="line"><span class="keyword">template</span> &lt;&gt; <span class="function"><span class="keyword">void</span> <span class="title">Swap</span><span class="params">(<span class="keyword">int</span> &amp;, <span class="keyword">int</span> &amp;)</span></span>;         <span class="comment">// explicit specialization</span></span><br></pre></td></tr></table></figure><p>这两个声明的意思是，不要使用Swap模板来生成函数定义，而应使用专门为int类型显式定义的函数定义。</p></li></ol><p><strong>警告</strong>：试图在同一个文件（或转换单元）中使用同一种类型的显示实例化和显示具体化将出错。</p><p>隐式实例化、显示实例化和显示具体化统称为具体化。它们的相同之处在于，它们表示的都是使用具体类型的函数定义，而不是通用描述。</p><p>一段代码总结隐式实例化、显示实例化和显示具体化：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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="comment">// ...</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* template prototype */</span></span><br><span class="line"><span class="keyword">template</span> &lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Swap</span><span class="params">(T &amp;, T&amp;)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* explicit specialization for job */</span></span><br><span class="line"><span class="keyword">template</span> &lt;&gt;</span><br><span class="line"><span class="keyword">void</span> Swap&lt;job&gt;(job &amp;, job &amp;);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">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="comment">// ...</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">/* explicit instantiation for char */</span></span><br><span class="line">    <span class="keyword">template</span> <span class="keyword">void</span> Swap&lt;<span class="keyword">char</span>&gt;(<span class="keyword">char</span> &amp;, <span class="keyword">char</span> &amp;);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">short</span> a, b;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    <span class="built_in">Swap</span>(a, b);  <span class="comment">// implicit template instantiation for short</span></span><br><span class="line">    </span><br><span class="line">    job n, m;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    <span class="built_in">Swap</span>(n, m);  <span class="comment">// use explicit specialization for job</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">char</span> g, h;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    <span class="built_in">Swap</span>(g, h);  <span class="comment">// use explicit template instantiation for char</span></span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="8-5-5-编译器选择使用哪个函数版本"><a href="#8-5-5-编译器选择使用哪个函数版本" class="headerlink" title="8.5.5 编译器选择使用哪个函数版本"></a>8.5.5 编译器选择使用哪个函数版本</h4><p>对于函数重载、函数模板以及函数模板重载，C++需要一个定义良好的策略，来决定为函数调用使用哪一个函数定义，尤其是有多个参数时。这个过程称为<strong>重载解析</strong>（overloading resolution）。</p><ul><li>第1步：创建候选函数列表。其中包含与被调用函数的名称相同的函数和模板函数。</li><li>第2步：使用候选函数列表创建可行函数列表。这些都是参数数目正确的函数，为此有一个隐式转换序列，其中包括实参类型与相应的形参类型完全匹配的情况。例如，使用float参数的函数调用可以将该参数转换为double，从而与double形参匹配，而模板可以为float生成一个实例。</li><li>第3步：确定是否有最佳的可行函数。如果有，则使用它，否则该函数调用出错。</li></ul><h5 id="完全匹配和最佳匹配"><a href="#完全匹配和最佳匹配" class="headerlink" title="完全匹配和最佳匹配"></a>完全匹配和最佳匹配</h5><p>完全匹配允许某些“无关紧要的转换”。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210406224402978.png" alt="image-20210406224402978"></p><p>如果有多个匹配的原型，则编译器将无法完成重载解析过程；如果没有最佳的可行函数，则编译器将生成一条错误信息，该消息可能会使用诸如“ambiguous（二义性）”这样的词语。</p><p>然而，有时候，即使两个函数都完全匹配，仍可完成重载解析：</p><ul><li>指向非const数据的指针和引用优先于指向const的指针和引用</li><li>非模板函数将优先于模板函数（包括显示具体化）</li></ul><p>术语<strong>最具体</strong>并不一定意味着显示具体化，而是指编译器推断使用哪种类型时执行的转换最少。</p><h5 id="自己选择"><a href="#自己选择" class="headerlink" title="自己选择"></a>自己选择</h5><p>使用<code>less&lt;&gt;(m, n)</code>显示指定编译器应使用模板，而不是非模板。</p><h4 id="8-5-6-模板函数的发展"><a href="#8-5-6-模板函数的发展" class="headerlink" title="8.5.6 模板函数的发展"></a>8.5.6 模板函数的发展</h4><h5 id="是什么类型"><a href="#是什么类型" class="headerlink" title="是什么类型"></a>是什么类型</h5><p>编写模板函数时，一个问题是并非总能知道应在声明中使用哪种类型：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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">tempalte &lt;<span class="keyword">typename</span> T1, <span class="keyword">typename</span> T2&gt;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">ft</span><span class="params">(T1 x, T2 y)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    ...</span><br><span class="line">    ?type? xpy = x + y;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>xpy应为什么类型？</p><h5 id="关键字decltype（C-11）"><a href="#关键字decltype（C-11）" class="headerlink" title="关键字decltype（C++11）"></a>关键字decltype（C++11）</h5><p>语法：</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="keyword">decltype</span>(expression) var;</span><br></pre></td></tr></table></figure><p>使用decltype，可以解决上述问题：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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">tempalte &lt;<span class="keyword">typename</span> T1, <span class="keyword">typename</span> T2&gt;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">ft</span><span class="params">(T1 x, T2 y)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="keyword">decltype</span>(x + y) xpy = x + y;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>decltype核对表：</p><ul><li><p>第1步：如果expression是一个没有括号括起的标识符，则var的类型与该标识符的类型相同，包括const等限定符：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">double</span> x = <span class="number">5.5</span>;</span><br><span class="line"><span class="keyword">double</span> y = <span class="number">7.9</span>;</span><br><span class="line"><span class="keyword">double</span> &amp;rx = x;</span><br><span class="line"><span class="keyword">const</span> <span class="keyword">double</span> *pd;</span><br><span class="line"><span class="keyword">decltype</span>(x) w;         <span class="comment">// w is type of double</span></span><br><span class="line"><span class="keyword">decltype</span>(rx) u = y;    <span class="comment">// u is type of double &amp;</span></span><br><span class="line"><span class="keyword">decltype</span>(pd) v;        <span class="comment">// v is type of const double *</span></span><br></pre></td></tr></table></figure></li><li><p>第2步：如果expression是一个函数调用，则var的类型与函数的返回类型相同：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">long</span> <span class="title">indeed</span><span class="params">(<span class="keyword">int</span>)</span></span>;</span><br><span class="line"><span class="keyword">decltype</span>(<span class="built_in">indeed</span>(<span class="number">3</span>)) m;  <span class="comment">// m is type of int</span></span><br></pre></td></tr></table></figure><p>注意，并不会实际调用函数。编译器通过查看函数的原型来获悉返回类型，而无需实际调用函数。</p></li><li><p>第3步：如果expression是一个被括号括起的标识符，则var为指向其类型的引用：</p><figure class="highlight c++"><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">double</span> xx = <span class="number">4.4</span>;</span><br><span class="line"><span class="keyword">decltype</span>((xx)) r2 = xx;  <span class="comment">// r2 is type of double &amp;</span></span><br><span class="line"><span class="keyword">decltype</span>(xx) w = xx;     <span class="comment">// w is type of double</span></span><br></pre></td></tr></table></figure></li><li><p>第4步：如果前面条件都不满足，则var的类型与expression的类型相同</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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">int</span> j = <span class="number">3</span>;</span><br><span class="line"><span class="keyword">int</span> &amp;k = j;</span><br><span class="line"><span class="keyword">int</span> &amp;n = j;</span><br><span class="line"><span class="keyword">decltype</span>(j + <span class="number">6</span>) i1;  <span class="comment">// i1 type int</span></span><br><span class="line"><span class="keyword">decltype</span>(<span class="number">100L</span>) i2;   <span class="comment">// i2 type long</span></span><br><span class="line"><span class="keyword">decltype</span>(k + n) i3;  <span class="comment">// i3 type int</span></span><br></pre></td></tr></table></figure></li></ul><p>如果需要多次声明，可使用<code>typedef</code>定义别名：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">tempalte &lt;<span class="keyword">typename</span> T1, <span class="keyword">typename</span> T2&gt;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">ft</span><span class="params">(T1 x, T2 y)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="function"><span class="keyword">typedef</span> <span class="title">decltype</span><span class="params">(x + y)</span> xytype</span>;</span><br><span class="line">    xytype xpy = x + y;</span><br><span class="line">    xytype arr[<span class="number">10</span>];</span><br><span class="line">    xytype &amp;rxy = arr[<span class="number">2</span>];</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="另一种函数声明语法（C-11后置返回类型）"><a href="#另一种函数声明语法（C-11后置返回类型）" class="headerlink" title="另一种函数声明语法（C++11后置返回类型）"></a>另一种函数声明语法（C++11后置返回类型）</h5><p>有一个问题是decltype本身无法解决的：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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">tempalte &lt;<span class="keyword">typename</span> T1, <span class="keyword">typename</span> T2&gt;</span><br><span class="line">?type? <span class="built_in">ft</span>(T1 x, T2 y)</span><br><span class="line">&#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="keyword">return</span> x + y;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>声明返回值类型时，此时还未声明参数x和y，它们不在作用域内。使用后置返回类型可以解决：</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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">tempalte &lt;<span class="keyword">typename</span> T1, <span class="keyword">typename</span> T2&gt;</span><br><span class="line">auto ft(T1 x, T2 y) -&gt; decltype(x + y)</span><br><span class="line">&#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="keyword">return</span> x + y;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">auto h(int x, float y) -&gt; double;</span><br></pre></td></tr></table></figure><p>后置返回类型可用于原型和定义。</p><h2 id="第9章-内存模型和命名空间"><a href="#第9章-内存模型和命名空间" class="headerlink" title="第9章 内存模型和命名空间"></a>第9章 内存模型和命名空间</h2><h3 id="9-1-单独编译"><a href="#9-1-单独编译" class="headerlink" title="9.1 单独编译"></a>9.1 单独编译</h3><p>头文件中常包含的内容：</p><ul><li>函数原型</li><li>使用<code>#define</code>或<code>const</code>定义的符号常量</li><li>结构声明</li><li>类声明</li><li>模板声明</li><li>内联函数</li></ul><p>被声明为const的数据和内联函数有特殊的链接属性，因此可以将其放在头文件中。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210407201324520.png" alt="image-20210407201324520"></p><p>上面这段文字也是为什么C++调用C的库函数时需要使用<code>extern &quot;C&quot;</code>的原因。</p><h3 id="9-2-存储持续性、作用域和链接性"><a href="#9-2-存储持续性、作用域和链接性" class="headerlink" title="9.2 存储持续性、作用域和链接性"></a>9.2 存储持续性、作用域和链接性</h3><p>C++使用三种（在C++11中是四种）不同的方案来存储数据，这些方案的区别就在于数据保留在内存中的时间。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210407202044977.png" alt="image-20210407202044977"></p><h4 id="9-2-1-作用域和链接"><a href="#9-2-1-作用域和链接" class="headerlink" title="9.2.1 作用域和链接"></a>9.2.1 作用域和链接</h4><p>作用域（scope）描述了名称在文件（翻译单元）的多大范围内可见。</p><ul><li>局部代码块</li><li>全局文件作用域</li><li>函数原型作用域</li><li>类作用域</li><li>命名空间作用域</li></ul><p>链接性（linkage）描述了名称如何在不同的翻译单元间共享。</p><ul><li>外部链接性：可在文件间共享</li><li>内部链接性：只能在一个文件的函数共享</li><li>无链接性：不能共享</li></ul><h4 id="9-2-2-自动存储持续性"><a href="#9-2-2-自动存储持续性" class="headerlink" title="9.2.2 自动存储持续性"></a>9.2.2 自动存储持续性</h4><p>默认情况下，在函数中声明的<strong>函数参数</strong>和<strong>变量</strong>的存储持续性为自动，作用域为局部，没有链接性。当程序开始执行这些变量所属的代码块时，将为其分配内存，但其作用域的起点为其声明位置；当函数结束时，这些变量都将消失。</p><h5 id="1-自动变量的初始化"><a href="#1-自动变量的初始化" class="headerlink" title="1. 自动变量的初始化"></a>1. 自动变量的初始化</h5><p>只声明而不初始化，其值是垃圾数据。</p><h5 id="2-自动变量和栈"><a href="#2-自动变量和栈" class="headerlink" title="2. 自动变量和栈"></a>2. 自动变量和栈</h5><p>自动变量存储在栈上。栈的默认大小取决于具体的实现，编译器通常提供改变栈长度的选项。当函数被调用时，自动变量将被加入到栈中，栈顶指针指向变量后面的下一个可用的内存单元。函数结束后，栈顶指针被重置问函数被调用前的值，从而释放新变量使用的内存。新值没有被删除，但不再被标记。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210407205054554.png" alt="image-20210407205054554"></p><h5 id="3-register"><a href="#3-register" class="headerlink" title="3. register"></a>3. register</h5><p>C++11之前，<code>register</code>关键字用来指示使用寄存器存储变量，目的在于提高访问变量的速度。C++11中，<code>register</code>关键字只是显式地指出变量是自动的。</p><h4 id="9-2-3-静态存储持续性"><a href="#9-2-3-静态存储持续性" class="headerlink" title="9.2.3 静态存储持续性"></a>9.2.3 静态存储持续性</h4><p>静态存储持续性变量有3中链接性：</p><ul><li>外部链接性：全局变量</li><li>内部链接性：static修饰的全局变量</li><li>无链接性：static修饰的局部变量</li></ul><p>由于静态存储持续性变量（简称静态变量）的数目在程序运行期间是不变的，因此程序不需要使用特殊的装置（如栈）来管理它们。编译器将分配固定的内存块来存储所有的静态变量，这些变量在整个程序执行期间一直存在。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210407210020408.png" alt="image-20210407210020408"></p><p>下面是三种静态变量的创建方式：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line"><span class="keyword">int</span> global = <span class="number">1000</span>;           <span class="comment">// static duration, external linkage</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">int</span> one_file = <span class="number">50</span>;    <span class="comment">// static duration, internal linkage</span></span><br><span class="line">...</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">func</span><span class="params">(<span class="keyword">int</span> n)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">int</span> count = <span class="number">0</span>;    <span class="comment">// static duration, no linkage</span></span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="1-静态持续性变量的初始化规则"><a href="#1-静态持续性变量的初始化规则" class="headerlink" title="1. 静态持续性变量的初始化规则"></a>1. 静态持续性变量的初始化规则</h5><p>如果没有显示初始化静态变量，编译器将把它们所有位初始化为0，这种变量被称为<strong>零初始化的（zero-initialized）</strong>。默认情况下，静态数组和结构将每个元素或成员的所有位设置为0。对于指针而言，0初始化可能并不是将指针的值设置为0，而是初始化为空指针在系统的内部表示。</p><p>零初始化和表达式初始化统称为<strong>静态初始化</strong>。这意味着在编译器处理翻译单元时初始化变量。动态初始化意味着变量将在编译后初始化。</p><p>对于所有的静态变量，无论是否有显示初始化，先进行零初始化。接下来，如果使用常量表达式初始化了变量，且编译器仅根据文件内容（包括被包含的头文件）就可计算表达式，编译器将执行常量表达式初始化。必要时，编译器执行简单计算。如果没有足够的信息，变量将被动态初始化。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;cmath&gt;</span></span></span><br><span class="line"><span class="keyword">int</span> x;                               <span class="comment">// 零初始化</span></span><br><span class="line"><span class="keyword">int</span> y = <span class="number">5</span>;                           <span class="comment">// 常量表达式初始化</span></span><br><span class="line"><span class="keyword">long</span> z = <span class="number">13</span> * <span class="number">13</span>;                    <span class="comment">// 常量表达式初始化</span></span><br><span class="line"><span class="keyword">const</span> <span class="keyword">double</span> pi = <span class="number">4.0</span> * <span class="built_in">atan</span>(<span class="number">1.0</span>);   <span class="comment">// 动态初始化</span></span><br><span class="line"><span class="keyword">int</span> enough = <span class="number">2</span> * <span class="built_in"><span class="keyword">sizeof</span></span>(<span class="keyword">long</span>) + <span class="number">1</span>;   <span class="comment">// 常量表达式初始化</span></span><br></pre></td></tr></table></figure><h5 id="2-静态持续性、外部链接性"><a href="#2-静态持续性、外部链接性" class="headerlink" title="2.  静态持续性、外部链接性"></a>2.  静态持续性、外部链接性</h5><p><strong>单定义规则</strong></p><p>一方面，在每个使用外部变量的文件中，都必须声明它；另一方面，C++有“单定义规则”，该规则指出，变量只能有一次定义。</p><ul><li>定义声明，简称定义，不给变量分配存储空间，因为它引用已有的变量。</li><li>引用声明，简称声明，给变量分配存储空间。使用关键字<code>extern</code>，且不进行初始化；否则，声明为定义，导致分配存储空间。</li></ul><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210407212956406.png" alt="image-20210407212956406"></p><p>注意，单定义规则并非意味着不能有多个变量的名称相同。例如，在不同函数中声明的同名自动变量是彼此独立的，它们都有自己的地址。局部变量可能隐藏同名的全局变量。虽然程序中可包含多个同名的变量，但每个变量都只有一个定义。</p><p>局部变量隐藏同名的全局变量时，可使用作用域解析运算符<code>::</code>来访问全局变量。</p><h5 id="3-静态持续性，内部链接性"><a href="#3-静态持续性，内部链接性" class="headerlink" title="3. 静态持续性，内部链接性"></a>3. 静态持续性，内部链接性</h5><p>使用static修饰全局变量，其链接性由外部链接性变成了内部链接性，且不与其他文件中的同名外部链接性全局变量冲突。</p><h5 id="4-静态持续性，无链接性"><a href="#4-静态持续性，无链接性" class="headerlink" title="4. 静态持续性，无链接性"></a>4. 静态持续性，无链接性</h5><p>使用static修饰局部变量，其链接性仍为无链接性，但其存储持续性由自动存储持续性变为静态存储持续性。</p><h4 id="9-2-4-说明符和限定符"><a href="#9-2-4-说明符和限定符" class="headerlink" title="9.2.4 说明符和限定符"></a>9.2.4 说明符和限定符</h4><p>存储说明符（storage class specifier）:</p><ul><li>auto（在C++11中已不再是说明符）</li><li>register</li><li>static</li><li>extern</li><li>thread_local</li><li>mutable</li></ul><h5 id="1-cv限定符"><a href="#1-cv限定符" class="headerlink" title="1 - cv限定符"></a>1 - cv限定符</h5><ul><li>const</li><li>volatile</li></ul><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210407221902734.png" alt="image-20210407221902734"></p><h5 id="2-mutable"><a href="#2-mutable" class="headerlink" title="2 - mutable"></a>2 - mutable</h5><p>可以用mutable指出，即使结构或类变量为const，其某个成员也可以被修改。例如：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><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="class"><span class="keyword">struct</span> <span class="title">data</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">    <span class="keyword">char</span> name[<span class="number">30</span>];</span><br><span class="line">    <span class="keyword">mutable</span> <span class="keyword">int</span> accesses;</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> data veep = &#123;<span class="string">&quot;Claybourne Clodde&quot;</span>, <span class="number">0</span>, ...&#125;;</span><br><span class="line">veep.accesses++;       <span class="comment">// allowed</span></span><br></pre></td></tr></table></figure><h5 id="3-再谈const"><a href="#3-再谈const" class="headerlink" title="3 - 再谈const"></a>3 - 再谈const</h5><p>const除了使被其修饰的变量不可修改，只可读不可写，还使全局变量的链接性变成内部链接性。因此可以在头文件中定义一组const变量，并在同一个程序的多个文件中包含该头文件。</p><p>出于某种原因，如果程序员希望某个全局const变量的链接性为外部的，可以使用extern关键字来覆盖默认的内部链接性：</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="keyword">extern</span> <span class="keyword">const</span> <span class="keyword">int</span> states = <span class="number">50</span>; <span class="comment">// 定义声明，外部链接性</span></span><br></pre></td></tr></table></figure><h4 id="9-2-5-函数和链接性"><a href="#9-2-5-函数和链接性" class="headerlink" title="9.2.5 函数和链接性"></a>9.2.5 函数和链接性</h4><p>所有函数的存储持续性为静态存储持续性。</p><p>函数的链接性默认为外部链接性，同样遵循单定义原则，只能有一个定义，可以有多个声明。将函数原型写在头文件中，并在其他使用该函数的文件中包含该头文件，就是多个声明，一个定义。可以使用static关键字修饰函数，使得其链接性为内部链接性，在原型和定义上都加上static。</p><p>内联函数不受但定义规则的约束，可能是因为inline关键字与const关键字和static关键字一样，将外部链接性改为了内部链接性，因此内联函数可以防在头文件中。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210407223616291.png" alt="image-20210407223616291"></p><h4 id="9-2-6-语言的链接性"><a href="#9-2-6-语言的链接性" class="headerlink" title="9.2.6 语言的链接性"></a>9.2.6 语言的链接性</h4><p>语言的链接性就是我们之前提到的C++不能直接调用C语言的库，因为编译器的名称修饰不同。更广泛的说，不同编译器编译出的目标文件不能直接相互链接，因为一个编译器与另一个编译器的名称修饰不同，导致找不到函数定义。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210407223848384.png" alt="image-20210407223848384"></p><h4 id="9-2-7-存储方案和动态分配"><a href="#9-2-7-存储方案和动态分配" class="headerlink" title="9.2.7 存储方案和动态分配"></a>9.2.7 存储方案和动态分配</h4><p>动态内存由运算符new和delete控制，而不是由作用域和链接性规则控制，内存分配和释放顺序取决于new和delete在何时以何种方式被使用。</p><p>通常，编译器使用三块内存：</p><ul><li>静态变量</li><li>自动变量</li><li>动态存储</li></ul><h5 id="1-使用new运算符初始化"><a href="#1-使用new运算符初始化" class="headerlink" title="1 - 使用new运算符初始化"></a>1 - 使用new运算符初始化</h5><p>C++98: 括号语法，也适用于有合适构造函数的类。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> *pi = <span class="keyword">new</span> <span class="built_in"><span class="keyword">int</span></span> (<span class="number">6</span>);            <span class="comment">// *pi is 6</span></span><br><span class="line"><span class="keyword">double</span> *pd = <span class="keyword">new</span> <span class="built_in"><span class="keyword">double</span></span> (<span class="number">99.99</span>);  <span class="comment">// *pd set to 99.99</span></span><br></pre></td></tr></table></figure><p>C++11：初始化列表</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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="class"><span class="keyword">struct</span> <span class="title">where</span> &#123;</span> <span class="keyword">double</span> x; <span class="keyword">double</span> y; <span class="keyword">double</span> z; &#125;;</span><br><span class="line">where *one = <span class="keyword">new</span> where &#123;<span class="number">2.5</span>, <span class="number">5.3</span>, <span class="number">7.2</span>&#125;;</span><br><span class="line"><span class="keyword">int</span> *ar = <span class="keyword">new</span> <span class="keyword">int</span> [<span class="number">4</span>] &#123;<span class="number">2</span>, <span class="number">4</span>, <span class="number">6</span>, <span class="number">7</span>&#125;;</span><br><span class="line"><span class="keyword">int</span> *pin = <span class="keyword">new</span> <span class="keyword">int</span> &#123;<span class="number">6</span>&#125;;</span><br><span class="line"><span class="keyword">int</span> *pdo = <span class="keyword">new</span> <span class="keyword">double</span> &#123;<span class="number">99.99</span>&#125;;</span><br></pre></td></tr></table></figure><h5 id="2-new失败时"><a href="#2-new失败时" class="headerlink" title="2 - new失败时"></a>2 - new失败时</h5><p>在最初的十年中，C++在这种情况下让new返回空指针，但现在将引发异常<code>std::bad_alloc</code>。</p><h4 id="3-new-运算符、函数和替换函数"><a href="#3-new-运算符、函数和替换函数" class="headerlink" title="3 - new: 运算符、函数和替换函数"></a>3 - new: 运算符、函数和替换函数</h4><p>运算符<code>new</code>和<code>new[]</code>分别调用如下函数：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> * <span class="keyword">operator</span> <span class="title">new</span><span class="params">(std::<span class="keyword">size_t</span>)</span></span>;</span><br><span class="line"><span class="keyword">void</span> * <span class="keyword">operator</span> <span class="keyword">new</span>[](std::<span class="keyword">size_t</span>);</span><br></pre></td></tr></table></figure><p>这些函数被称为分配函数，位于全局命名空间中。同样，也有由<code>delete</code>和<code>delete[]</code>调用的释放函数：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="keyword">operator</span> <span class="title">delete</span><span class="params">(<span class="keyword">void</span> *)</span></span>;</span><br><span class="line"><span class="keyword">void</span> <span class="keyword">operator</span> <span class="keyword">delete</span>[](<span class="keyword">void</span> *);</span><br></pre></td></tr></table></figure><p><code>int *pi = new int;</code>将被转换成<code>int *pi = new(sizeof(int));</code>；而<code>int *pa = new int [40];</code>将被转换成<code>int *pa = new(40 * sizeof(int));</code>。</p><p>同样，对于<code>delete pi;</code>，将转换成函数调用<code>delete(pi);</code>。</p><h5 id="4-定位new运算符"><a href="#4-定位new运算符" class="headerlink" title="4 - 定位new运算符"></a>4 - 定位new运算符</h5><p>定位new运算符能够让程序员指定要使用的位置。要使用定位new运算符，首先需要包含头文件new，它提供了这种版本的new运算符的原型，然后将new运算符用于提供了所需地址的参数。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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="comment">// newplace.cpp -- using placement new</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;new&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> BUF = <span class="number">512</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> N = <span class="number">5</span>;</span><br><span class="line"><span class="keyword">char</span> buffer[BUF];</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">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="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line">    <span class="keyword">double</span> *pd1, *pd2;</span><br><span class="line">    <span class="keyword">int</span> i;</span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Calling new and placement new:\n&quot;</span>;</span><br><span class="line">    pd1 = <span class="keyword">new</span> <span class="keyword">double</span>[N];</span><br><span class="line">    pd2 = <span class="built_in"><span class="keyword">new</span></span> (buffer) <span class="keyword">double</span>[N];</span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; N; i++)</span><br><span class="line">        pd2[i] = pd1[i] = <span class="number">1000</span> + <span class="number">20.0</span> * i;</span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Memory addresses:\n&quot;</span> &lt;&lt; <span class="string">&quot; heap: &quot;</span> &lt;&lt; pd1</span><br><span class="line">         &lt;&lt; <span class="string">&quot; static: &quot;</span> &lt;&lt; (<span class="keyword">void</span> *)buffer &lt;&lt; endl;</span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Memory contents:\n&quot;</span>;</span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; N; i++)</span><br><span class="line">    &#123;</span><br><span class="line">        cout &lt;&lt; pd1[i] &lt;&lt; <span class="string">&quot; at &quot;</span> &lt;&lt; &amp;pd1[i] &lt;&lt; <span class="string">&quot;; &quot;</span>;</span><br><span class="line">        cout &lt;&lt; pd2[i] &lt;&lt; <span class="string">&quot; at &quot;</span> &lt;&lt; &amp;pd2[i] &lt;&lt; endl;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;\nCalling new and placement new a second time:\n&quot;</span>;</span><br><span class="line">    <span class="keyword">double</span> *pd3, *pd4;</span><br><span class="line">    pd3 = <span class="keyword">new</span> <span class="keyword">double</span>[N];                 <span class="comment">// find new address</span></span><br><span class="line">    pd4 = <span class="built_in"><span class="keyword">new</span></span> (buffer) <span class="keyword">double</span>[N];        <span class="comment">// overwrite old data</span></span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; N; i++)</span><br><span class="line">        pd4[i] = pd3[i] = <span class="number">1000</span> + <span class="number">40.0</span> * i;</span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Memory contents:\n&quot;</span>;</span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; N; i++)</span><br><span class="line">    &#123;</span><br><span class="line">        cout &lt;&lt; pd3[i] &lt;&lt; <span class="string">&quot; at &quot;</span> &lt;&lt; &amp;pd3[i] &lt;&lt; <span class="string">&quot;; &quot;</span>;</span><br><span class="line">        cout &lt;&lt; pd4[i] &lt;&lt; <span class="string">&quot; at &quot;</span> &lt;&lt; &amp;pd4[i] &lt;&lt; endl;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;\nCalling new and placement new a third time:\n&quot;</span>;</span><br><span class="line">    <span class="keyword">delete</span>[] pd1;</span><br><span class="line">    pd1 = <span class="keyword">new</span> <span class="keyword">double</span>[N];</span><br><span class="line">    pd2 = <span class="built_in"><span class="keyword">new</span></span> (buffer + N * <span class="built_in"><span class="keyword">sizeof</span></span>(<span class="keyword">double</span>)) <span class="keyword">double</span>[N];</span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; N; i++)</span><br><span class="line">        pd2[i] = pd1[i] = <span class="number">1000</span> + <span class="number">60.0</span> * i;</span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Memory contents:\n&quot;</span>;</span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; N; i++)</span><br><span class="line">    &#123;</span><br><span class="line">        cout &lt;&lt; pd1[i] &lt;&lt; <span class="string">&quot; at &quot;</span> &lt;&lt; &amp;pd1[i] &lt;&lt; <span class="string">&quot;; &quot;</span>;</span><br><span class="line">        cout &lt;&lt; pd2[i] &lt;&lt; <span class="string">&quot; at &quot;</span> &lt;&lt; &amp;pd2[i] &lt;&lt; endl;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">delete</span>[] pd1;</span><br><span class="line">    <span class="keyword">delete</span>[] pd3;</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>上面是一段示例程序，输出结果如下：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">Calling new and placement new:</span><br><span class="line">Memory addresses:</span><br><span class="line"> heap: 0x1c1440 static: 0x408040</span><br><span class="line">Memory contents:</span><br><span class="line">1000 at 0x1c1440; 1000 at 0x408040</span><br><span class="line">1020 at 0x1c1448; 1020 at 0x408048</span><br><span class="line">1040 at 0x1c1450; 1040 at 0x408050</span><br><span class="line">1060 at 0x1c1458; 1060 at 0x408058</span><br><span class="line">1080 at 0x1c1460; 1080 at 0x408060</span><br><span class="line"></span><br><span class="line">Calling new and placement new a second time:</span><br><span class="line">Memory contents:</span><br><span class="line">1000 at 0x1c1470; 1000 at 0x408040</span><br><span class="line">1040 at 0x1c1478; 1040 at 0x408048</span><br><span class="line">1080 at 0x1c1480; 1080 at 0x408050</span><br><span class="line">1120 at 0x1c1488; 1120 at 0x408058</span><br><span class="line">1160 at 0x1c1490; 1160 at 0x408060</span><br><span class="line"></span><br><span class="line">Calling new and placement new a third time:</span><br><span class="line">Memory contents:</span><br><span class="line">1000 at 0x1c1440; 1000 at 0x408068</span><br><span class="line">1060 at 0x1c1448; 1060 at 0x408070</span><br><span class="line">1120 at 0x1c1450; 1120 at 0x408078</span><br><span class="line">1180 at 0x1c1458; 1180 at 0x408080</span><br><span class="line">1240 at 0x1c1460; 1240 at 0x408088</span><br></pre></td></tr></table></figure><p>对于常规new运算符而言，如果不使用delete释放已分配的堆上内存，则再次使用new运算符分配内存时，将会在堆上寻找一块新的内存空间。使用常规new运算符分配的内存空间要使用delete释放，否则会发生内存泄漏。</p><p>对于定位new运算符而言，上面的代码所给出的地址为静态存储区的地址，因此不能使用delete运算符进行释放，delete只能用于指向常规new运算符分配的堆内存。另外，定位new运算符不会检查给它的地址空间已被使用，而是直接使用该地址进行覆盖分配。</p><p>定位new运算符可与初始化结合使用，从而将信息放在特定的硬件地址处。</p><blockquote><p>默认的定位new运算符工作原理：它只是返回传递给它的地址，并将其强制转换为<code>void *</code>，以便能够赋给任何指针类型。当然，这只是默认定位new运算符，C++允许程序员重载定位new函数。</p></blockquote><p>原型：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210408101928437.png" alt="image-20210408101928437"></p><h3 id="9-3-名称空间"><a href="#9-3-名称空间" class="headerlink" title="9.3 名称空间"></a>9.3 名称空间</h3><h4 id="9-3-1-传统的C-名称空间"><a href="#9-3-1-传统的C-名称空间" class="headerlink" title="9.3.1 传统的C++名称空间"></a>9.3.1 传统的C++名称空间</h4><ul><li>声明区域（declaration region）：声明区域是可以在其中进行声明的区域</li><li>潜在作用域（potential scope）：变量的潜在作用域从声明点开始，到其声明区域的结尾</li><li>作用域（scope）：变量对程序而言可见的范围被称为作用域</li></ul><p>潜在作用域比声明区域小，这是由于变量必须定义后才能使用。变量并非在其潜在作用域内的任何位置都是可见的，例如，它可能被另一个在嵌套声明区域中声明的同名变量隐藏，或者在函数中声明的局部变量将隐藏在同一个文件中声明的全局变量。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210408103129019.png" alt="image-20210408103129019"></p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210408103141409.png" alt="image-20210408103141409"></p><p>C++关于全局变量和局部变量的规则定义了一种名称空间层次。每个声明区域都可以声明名称，这些名称独立于在其他声明区域中声明的名称。在一个函数中声明的局部变量不会与另一个函数中声明的局部变量发生冲突。</p><h4 id="9-3-2-新的名称空间特性"><a href="#9-3-2-新的名称空间特性" class="headerlink" title="9.3.2 新的名称空间特性"></a>9.3.2 新的名称空间特性</h4><p>C++新增了这样一种功能，即通过定义一种新的声明区域来创建命名的名称空间，这样做的目的之一是提供一个新的声明名称的区域。一个名称空间中的名称不会与另一个名称空间的相同名称发生冲突，同时允许程序的其他部分使用该名称空间中声明的东西。</p><p>名称空间可以是全局的，也可位于另一个名称空间中，但不能位于代码块中。默认情况下，在名称空间中声明的名称的链接性为外部的（除非它引用了常量）。</p><p>除了用户定义的名称空间外，还存在另一个名称空间——全局名称空间（global namespace）。它对应于文件级声明区域，因此前面所说的全局变量现在被描述为位于全局名称空间中。</p><p>任何名称空间中的名称都不会与其他名称空间中的名称发生冲突。</p><p>访问方式：作用域解析符<code>::</code>。</p><h5 id="1-using声明和using编译指令"><a href="#1-using声明和using编译指令" class="headerlink" title="1 - using声明和using编译指令"></a>1 - using声明和using编译指令</h5><p>C++提供两种机制（using声明和using编译指令）来简化对名称空间中名称的使用。</p><ul><li>using声明使特定的标识符可用：using声明将特定的名称添加到它所属的声明区域中。<code>using std::cin;</code>。</li><li>using编译指令使整个名称空间可用。<code>using namespace std;</code>。</li></ul><h5 id="2-using编译指令和using声明之比较"><a href="#2-using编译指令和using声明之比较" class="headerlink" title="2 - using编译指令和using声明之比较"></a>2 - using编译指令和using声明之比较</h5><p>使用using编译指令导入一个名称空间中所有的名称与使用多个using声明是不一样的，using编译指令更像是大量使用作用域解析运算符。</p><p>使用using声明时，就好像声明了相应的名称一样，如果某个名称已经在函数中声明了，则不能使用using声明导入相同的名称。</p><p>然而，使用using编译指令时，将进行名称解析，<span style="color: red">就像在包含using声明和名称空间本身的最小声明区域中声明了名称一样</span>。看下面的代码就能明白：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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="keyword">namespace</span> Jill &#123;</span><br><span class="line">    <span class="function"><span class="keyword">double</span> <span class="title">bucket</span><span class="params">(<span class="keyword">double</span> n)</span> </span>&#123; ... &#125;</span><br><span class="line">    <span class="keyword">double</span> fetch;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">Hill</span> &#123;</span> ... &#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">char</span> fetch;  <span class="comment">// global namespace</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">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="keyword">using</span> <span class="keyword">namespace</span> Jill;      <span class="comment">// hide global fetch</span></span><br><span class="line">    Hill Thrill;               <span class="comment">// create a type Jill::Hill structure</span></span><br><span class="line">    <span class="keyword">double</span> water = <span class="built_in">bucket</span>(<span class="number">2</span>);  <span class="comment">// use Jill::bucket();</span></span><br><span class="line">    <span class="keyword">double</span> fetch;              <span class="comment">// not an error; hides Jill::fetch</span></span><br><span class="line">    cin &gt;&gt; fetch;              <span class="comment">// read a value into the local fetch</span></span><br><span class="line">    cin &gt;&gt; ::fetch;            <span class="comment">// read a value into the global fetch</span></span><br><span class="line">    cin &gt;&gt; Jill::fetch;        <span class="comment">// read a value into Jill::fetch</span></span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">foo</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    Hill top;          <span class="comment">// error</span></span><br><span class="line">    Jill::Hill crest;  <span class="comment">// Valid</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">bar</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">double</span> fetch;</span><br><span class="line">    <span class="keyword">using</span> Jill::fetch; <span class="comment">// error</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在函数中使用using编译指令时，命名空间变量覆盖全局变量，而局部变量覆盖命名空间变量。</p><h5 id="3-名称空间的其他特性"><a href="#3-名称空间的其他特性" class="headerlink" title="3 - 名称空间的其他特性"></a>3 - 名称空间的其他特性</h5><p>可以将名称空间声明进行嵌套：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">namespace</span> elements</span><br><span class="line">&#123;</span><br><span class="line">    namespece fire</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">int</span> flame;</span><br><span class="line">        ...</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">float</span> water;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以在名称空间中使用using编译指令和using声明：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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">namespace</span> myth</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">using</span> Jill::fetch;</span><br><span class="line">    <span class="keyword">using</span> <span class="keyword">namespace</span> elelemts;</span><br><span class="line">    <span class="keyword">using</span> std::cout;</span><br><span class="line">    <span class="keyword">using</span> std::cin;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>uisng编译指令时可以传递的：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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">using</span> <span class="keyword">namespace</span> myth;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 等价于</span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> myth;</span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> elements;</span><br></pre></td></tr></table></figure><p>可以给名称空间创建别名：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> alias = myth;</span><br><span class="line"><span class="keyword">namespace</span> MEF = myth::elements::file;</span><br><span class="line"><span class="keyword">using</span> MEF::flame;</span><br></pre></td></tr></table></figure><h5 id="4-未命名的名称空间"><a href="#4-未命名的名称空间" class="headerlink" title="4 - 未命名的名称空间"></a>4 - 未命名的名称空间</h5><p>可以通过省略名称空间的名称来创建未命名的名称空间：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">int</span> ice;</span><br><span class="line">    <span class="keyword">int</span> bandycoot;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这就像后面跟着using编译指令一样，也就是说，在该名称空间声明的名称的潜在作用域为从声明点到该声明区域末尾。从这个方面看，它们与全局变量相似，但是，由于没有名称，因此不能显示使用using编译指令或using声明来使它们在其他位置都可用。具体地说，不能在未命名名称空间所属文件之外地其他文件中，使用该名称空间中的名称。这提供了链接性为内部的静态变量的替代品。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">static</span> <span class="keyword">int</span> counts;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">other</span><span class="params">()</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">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><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">other</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>采用名称空间的方法：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">int</span> counts;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">other</span><span class="params">()</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">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><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">other</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="5-命名空间及其用途"><a href="#5-命名空间及其用途" class="headerlink" title="5 - 命名空间及其用途"></a>5 - 命名空间及其用途</h5><p>多个文件中，多个同名的名称空间实际上都是一个名称空间，这就是为什么名称空间可以写在头文件中的原因。</p><p>下面是当前的一些指导原则：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210408121244544.png" alt="image-20210408121244544"></p><p>老式头文件（如stdio.h）没有使用命名空间，但新头文件cstdio使用了名称空间。</p><h2 id="第10章-对象和类"><a href="#第10章-对象和类" class="headerlink" title="第10章 对象和类"></a>第10章 对象和类</h2><p>面向对象4大特性：</p><ul><li>抽象</li><li>封装</li><li>继承</li><li>多态</li></ul><h3 id="10-1-抽象和类"><a href="#10-1-抽象和类" class="headerlink" title="10.1 抽象和类"></a>10.1 抽象和类</h3><h4 id="10-1-1-类型是什么？"><a href="#10-1-1-类型是什么？" class="headerlink" title="10.1.1 - 类型是什么？"></a>10.1.1 - 类型是什么？</h4><p>基本类型完成了三项定义工作：</p><ul><li>决定数据对象需要的内存数量</li><li>决定如何解释内存中的位</li><li>决定可使用数据对象执行的操作或方法</li></ul><h4 id="10-1-2-C-中的类"><a href="#10-1-2-C-中的类" class="headerlink" title="10.1.2 - C++中的类"></a>10.1.2 - C++中的类</h4><p>类是一种将抽象转换为用户定义类型的C++工具，它将数据表示和操纵数据的方法组合成一个整洁的包。一般来说，类规范由两个部分组成：</p><ul><li>类声明：以数据成员的方式描述数据部分，以成员函数的方式描述共有接口</li><li>类方法定义：描述如何实现类成员函数</li></ul><p>简单地说，类声明提供了类的蓝图，而方法定义则提供了细节。通常，C++程序员将类定义放在头文件中，并将类方法的代码放在源代码中。</p><p>数据隐藏是一种封装，将实现的细节隐藏在私有部分中。将类函数定义和类声明放在不同的文件中也是封装。</p><p>C++类的成员访问控制权限默认为<code>private</code>。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210409143519747.png" alt="image-20210409143519747"></p><h4 id="10-1-3-实现类的成员函数"><a href="#10-1-3-实现类的成员函数" class="headerlink" title="10.1.3 实现类的成员函数"></a>10.1.3 实现类的成员函数</h4><p>成员函数定义与常规函数定义非常相似，它们有函数头和函数体，也可以有返回类型和参数，但它们还有两个特殊的特征：</p><ul><li>定义成员函数时，使用作用域解析运算符<code>::</code>来标识函数所属的类</li><li>类的成员函数可以访问类的private组件</li></ul><h5 id="内联方法"><a href="#内联方法" class="headerlink" title="内联方法"></a>内联方法</h5><p>定义位于类声明中的函数都将自动成为内联函数。类声明常将短小的成员函数作为内联函数。</p><p>如果愿意，也可以在类声明之外定义成员函数，并使其成为内联函数。为此，只需在类实现部分中定义函数时使用<code>inline</code>限定符即可。</p><p>根据改写规则（rewrite rule），在类声明中定义方法等同于用原型替换方法定义，然后再类声明的后面将定义改写为内联函数。也就是说，下面两段代码实际上是等价的：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">// stock00.h</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> once</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifndef</span> STOCK00_H_</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> STOCK00_H_</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Stock</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">set_tot</span><span class="params">()</span> </span>&#123; total_val = shares * share_val; &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">// stock00.h</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> once</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifndef</span> STOCK00_H_</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> STOCK00_H_</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Stock</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">set_tot</span><span class="params">()</span></span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// stock00.cpp</span></span><br><span class="line"><span class="function"><span class="keyword">inline</span> <span class="keyword">void</span> <span class="title">Stock::set_tot</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    total_val = shares * share_val;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="类对象"><a href="#类对象" class="headerlink" title="类对象"></a>类对象</h5><p>所创建的每个新对象都有自己的存储空间，用于存储其内部变量和类成员；但同一个类的所有对象共享一组类方法，即每个方法只有一个副本。</p><h3 id="10-3-类的构造函数和析构函数"><a href="#10-3-类的构造函数和析构函数" class="headerlink" title="10.3 类的构造函数和析构函数"></a>10.3 类的构造函数和析构函数</h3><h4 id="10-3-1-声明和定义构造函数"><a href="#10-3-1-声明和定义构造函数" class="headerlink" title="10.3.1 声明和定义构造函数"></a>10.3.1 声明和定义构造函数</h4><p>构造函数没有返回类型，连<code>void</code>都不能带，函数名为类名。</p><h4 id="10-3-2-使用构造函数"><a href="#10-3-2-使用构造函数" class="headerlink" title="10.3.2 使用构造函数"></a>10.3.2 使用构造函数</h4><p>显示调用：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Stock food = <span class="built_in">Stock</span>(<span class="string">&quot;World Cabbage&quot;</span>, <span class="number">250</span>, <span class="number">1.25</span>);</span><br></pre></td></tr></table></figure><p>隐式调用：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">Stock <span class="title">food</span><span class="params">(<span class="string">&quot;World Cabbage&quot;</span>, <span class="number">250</span>, <span class="number">1.25</span>)</span></span>;</span><br></pre></td></tr></table></figure><p>显示调用和隐式调用二者是等价的。</p><p>创建指针变量：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Stock *pstock = <span class="keyword">new</span> <span class="built_in">Stock</span>(<span class="string">&quot;Electroshock Games&quot;</span>, <span class="number">18</span>, <span class="number">19.0</span>);</span><br></pre></td></tr></table></figure><p>无法使用对象来调用构造函数，因为在构造函数构造出对象之前，对象是不存在的。因此构造函数被用来创建对象，而不能通过对象来调用。</p><h4 id="10-3-3-默认构造函数"><a href="#10-3-3-默认构造函数" class="headerlink" title="10.3.3 默认构造函数"></a>10.3.3 默认构造函数</h4><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">Stock::<span class="built_in">Stock</span>() &#123; &#125;</span><br></pre></td></tr></table></figure><p>如果提供了非默认构造函数，则编译器不再提供默认构造函数，如果需要，需要自己动手写，否则，在未提供默认构造函数的情况下调用默认构造函数将出错。</p><p>定义默认构造函数的方式有两种：</p><ul><li><p>给已有的构造函数的所有参数提供默认值：</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">Stock</span>(<span class="keyword">const</span> string &amp;co = <span class="string">&quot;&quot;</span>, <span class="keyword">int</span> n = <span class="number">0</span>, <span class="keyword">double</span> pr = <span class="number">0.0</span>) &#123; ... &#125;</span><br></pre></td></tr></table></figure></li><li><p>通过函数重载来定义另一个构造函数——一个没有参数的构造函数：</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">Stock</span>() &#123; ... &#125;</span><br></pre></td></tr></table></figure></li></ul><p>由于只能有一个默认构造函数，因此不要同时采用这两种方式。</p><blockquote><p>在设计类时，通常应提供对所有类成员做隐式初始化的默认构造函数。</p></blockquote><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">Stock first;                  <span class="comment">// OK, call default constructor implicitly</span></span><br><span class="line"><span class="function">Stock <span class="title">second</span><span class="params">()</span></span>;               <span class="comment">// Declare a function, a function prototype</span></span><br><span class="line">Stock third = <span class="built_in">Stock</span>();        <span class="comment">// OK, call default constructor explicitly</span></span><br><span class="line">Stock forth = Stock;          <span class="comment">// INVALID</span></span><br><span class="line">Stock *prelief = <span class="keyword">new</span> Stock;   <span class="comment">// OK, call default constructor implicitly</span></span><br><span class="line">Stock *ps = <span class="keyword">new</span> <span class="built_in">Stock</span>();      <span class="comment">// OK, call default constructor explicitly</span></span><br></pre></td></tr></table></figure><h4 id="10-3-4-析构函数"><a href="#10-3-4-析构函数" class="headerlink" title="10.3.4 析构函数"></a>10.3.4 析构函数</h4><p>用构造函数创建过对象后，程序负责跟踪该对象，直到其过期为止。对象过期时，程序将自动调用一个特殊的成员函数，该函数叫做析构函数。析构函数完成清理工作，因此实际上很有用。例如，如果构造函数使用new来分配内存，则析构函数将使用delete来释放这些内存。</p><p>如果程序员没有提供析构函数，编译器将隐式地声明一个默认析构函数，并在发现导致对象被删除地代码后，提供默认析构函数的定义。</p><p>析构函数的名称为<code>~</code>加上类名，且不带任何参数，无返回类型：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// prototype</span></span><br><span class="line">~<span class="built_in">Stock</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">// definition</span></span><br><span class="line">Stock::~<span class="built_in">Stock</span>() &#123; ... &#125;</span><br></pre></td></tr></table></figure><p>什么时候应调用析构函数呢？这由编译器决定。通常不应在代码中显示地调用析构函数（也有例外情况）。</p><ul><li>如果创建的是静态存储类对象，则其析构函数在程序结束时自动被调用。</li><li>如果创建的是自动存储类对象，则其析构函数在程序执行完代码块（该对象是在其中定义的）时自动被调用。</li><li>如果对象是通过new创建的，则它将驻留在栈内存或自由存储区中，当使用delete来释放内存时，其析构函数将自动被调用。</li><li>程序可以创建临时对象来完成特定的操作，在这种情况下，程序将在结束对该对象的使用时自动调用其析构函数。</li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Stock stock1 = <span class="built_in">Stock</span>(<span class="string">&quot;Boffo Objects&quot;</span>, <span class="number">2</span>, <span class="number">2.0</span>);  <span class="comment">// initialization, maybe create a temporary variable, maybe not</span></span><br><span class="line">stock1 = <span class="built_in">Stock</span>(<span class="string">&quot;Nifty Foods&quot;</span>, <span class="number">10</span>, <span class="number">50.0</span>);   <span class="comment">// assignment, a temporary variable created.</span></span><br></pre></td></tr></table></figure><h4 id="10-3-5-C-11列表初始化"><a href="#10-3-5-C-11列表初始化" class="headerlink" title="10.3.5 C++11列表初始化"></a>10.3.5 C++11列表初始化</h4><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Stock hot_tip = &#123;<span class="string">&quot;Derivatives Plus Plus&quot;</span>, <span class="number">100</span>, <span class="number">45.0</span>&#125;;  <span class="comment">// 调用有参构造函数</span></span><br><span class="line">Stock jock &#123;<span class="string">&quot;Sport Age Storage, Inc&quot;</span>&#125;;                 <span class="comment">// 调用有参构造函数</span></span><br><span class="line">Stock temp &#123;&#125;;                                         <span class="comment">// 调用默认构造函数</span></span><br></pre></td></tr></table></figure><h4 id="10-3-6-const成员函数"><a href="#10-3-6-const成员函数" class="headerlink" title="10.3.6 const成员函数"></a>10.3.6 const成员函数</h4><p>const对象将无法调用共有的常规成员函数，因为常规成员函数无法保证const对象不被修改。为此，C++的解决方案是将const关键字放在函数的括号后面。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><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="class"><span class="keyword">class</span> <span class="title">Stock</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    ...</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">show</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Stock::show</span><span class="params">()</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>以这种方式声明和定义的成员函数被称为const成员函数。只要成员方法不修改调用对象，就应该将其声明为const。</p><p>const对象只能调用const成员函数，而非const对象既可以调用非const成员函数，也可以调用const成员函数。该const实际上是将成员函数的this指针限定为const。</p><h3 id="10-4-this指针"><a href="#10-4-this指针" class="headerlink" title="10.4 this指针"></a>10.4 this指针</h3><p>this指针指向用来调用成员函数的对象（this被作为隐藏参数传递给方法）。一般来讲，所有的类方法都将this指针设置为调用它的对象的地址。</p><p>每个成员函数（包括构造函数和析构函数）都有一个this指针。this指针指向调用对象。如果方法需要引用整个调用对象，则可以使用表达式<code>*this</code>。在函数的括号后面使用const限定符将this限定为const，这样将不能使用this来修改对象的值。</p><h3 id="10-5-对象数组"><a href="#10-5-对象数组" class="headerlink" title="10.5 对象数组"></a>10.5 对象数组</h3><p><strong>要创建对象数组，这个类必须有默认构造函数。</strong>初始化对象数组的方案是，首先使用默认构造函数创建数组元素，如果有列表初始化，则花括号中的构造函数将创建临时对象，然后将临时对象的内容复制到相应的元素中。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">Stock mystuff[<span class="number">4</span>];   <span class="comment">// create 4 Stock object by default constructor.</span></span><br><span class="line">Stock stocks[<span class="number">4</span>] = &#123;  <span class="comment">// create 4 Stock object by default constructor and copy from temporary object</span></span><br><span class="line"><span class="built_in">Stock</span>(<span class="string">&quot;NanoSmart&quot;</span>, <span class="number">12.5</span>, <span class="number">20</span>),</span><br><span class="line">    <span class="built_in">Stock</span>(),</span><br><span class="line">    <span class="built_in">Stock</span>(<span class="string">&quot;Monolithic Obelisks&quot;</span>, <span class="number">130</span>, <span class="number">3.25</span>),</span><br><span class="line">    <span class="built_in">Stock</span>(<span class="string">&quot;Fleep Enterprises&quot;</span>, <span class="number">60</span>, <span class="number">6.5</span>)</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="10-6-类作用域"><a href="#10-6-类作用域" class="headerlink" title="10.6 类作用域"></a>10.6 类作用域</h3><p>C++引入了一种新的作用域：类作用域。在类中定义的名称（如类数据成员名和类成员函数名）的作用域都为整个类，作用域为整个类的名称只会在该类中是已知的，在类外是不可知的。因此，可以在不同类中使用相同的类成员名而不会引起冲突。另外，类作用域意味着不能从外部直接访问类的成员，公有成员函数也是如此。要调用公有成员函数，必须通过对象。而在定义成员函数时，必须使用作用域解析运算符。</p><h5 id="10-6-1-作用域为类的常量"><a href="#10-6-1-作用域为类的常量" class="headerlink" title="10.6.1 作用域为类的常量"></a>10.6.1 作用域为类的常量</h5><p>在类中无法直接使用const声明常量。也就是说，下面的代码将会编译错误：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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="class"><span class="keyword">class</span> <span class="title">Bakery</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">int</span> Months = <span class="number">12</span>;</span><br><span class="line">    <span class="keyword">double</span> costs[Months];  <span class="comment">// compile error</span></span><br><span class="line">    ...</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>错误信息为”非静态成员引用必须与特定对象相对”。声明类只是描述了对象的形式，并没有创建对象，在创建对象前，将没有用于存储值的空间。</p><p>在类中定义常量有两种方式：</p><ul><li><p>在类中声明一个枚举。在类声明中声明的枚举的作用域为整个类，因此可以用枚举为整型常量提供作用域为整个类的符号名称</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Bakery</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="class"><span class="keyword">enum</span> &#123;</span> Months = <span class="number">12</span> &#125;;</span><br><span class="line">    <span class="keyword">double</span> costs[Months];</span><br><span class="line">    ...</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>注意，用这种方式声明枚举不会创建类数据成员，也就是说，所有对象中都不包含枚举。Months只是一个符号名称，在作用域为整个类的代码中遇到它时，编译器将用12来替换它。</p></li><li><p>使用static关键字</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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="class"><span class="keyword">class</span> <span class="title">Bakery</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">const</span> <span class="keyword">int</span> Months = <span class="number">12</span>;</span><br><span class="line">    <span class="keyword">double</span> costs[Months];</span><br><span class="line">    ...</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>这将创建一个名为Months的常量，该常量将与其他静态变量存储在一起，而不是存储在对象中。因此，只有一个Months常量，被所有Bakery对象共享。在C++98中，只能使用这种技术声明值为整数或枚举的静态常量，而不能存储double常量。C++11消除了这种限制。</p></li></ul><h5 id="10-6-2-作用域内枚举（C-11）"><a href="#10-6-2-作用域内枚举（C-11）" class="headerlink" title="10.6.2 作用域内枚举（C++11）"></a>10.6.2 作用域内枚举（C++11）</h5><p>传统枚举存在一些问题，其中之一是两个枚举定义中的枚举量可能发生冲突:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">enum</span> <span class="title">egg</span> &#123;</span> Small, Medium, Large, Jumbo &#125;;</span><br><span class="line"><span class="class"><span class="keyword">enum</span> <span class="title">t_shirt</span> &#123;</span> Small, Medium, Large, Xlarge &#125;;</span><br></pre></td></tr></table></figure><p>这无法通过编译，因为egg的Small和t_shirt的Small位于同一作用域内，它们将发生冲突。作用域内枚举：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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="class"><span class="keyword">enum</span> <span class="keyword">class</span> <span class="title">egg</span> &#123;</span> Small, Medium, Large, Jumbo &#125;;</span><br><span class="line"><span class="class"><span class="keyword">enum</span> <span class="keyword">class</span> <span class="title">t_shirt</span> &#123;</span> Small, Medium, Large, Xlarge &#125;;</span><br><span class="line"></span><br><span class="line">egg choice = egg::Large;</span><br><span class="line">t_shirt Floyd = t_shirt::Large;</span><br></pre></td></tr></table></figure><p>作用域枚举消除了冲突，此外，还提高了枚举的类型安全，不能隐式转换为整型：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">enum</span> <span class="title">egg_old</span> &#123;</span> Small, Medium, Large, Jumbo &#125;;</span><br><span class="line"><span class="class"><span class="keyword">enum</span> <span class="keyword">class</span> <span class="title">t_shirt</span> &#123;</span> Small, Medium, Large, Xlarge &#125;;</span><br><span class="line">egg_old one = Medium;</span><br><span class="line">t_shirt rolf = t_shirt::Large;</span><br><span class="line"><span class="keyword">int</span> king = one;   <span class="comment">// OK</span></span><br><span class="line"><span class="keyword">int</span> ring = rolf;  <span class="comment">// INVALID</span></span><br><span class="line"><span class="keyword">if</span> (king &lt; Jumbo) <span class="comment">// OK</span></span><br><span class="line">    ...</span><br><span class="line"><span class="keyword">if</span> (king &lt; t_shirt::Medium)  <span class="comment">// INVALID</span></span><br><span class="line">    ...</span><br></pre></td></tr></table></figure><p>但必要时，可执行强制类型转换：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> Frodo = <span class="built_in"><span class="keyword">int</span></span>(t_shirt::Small);  <span class="comment">// OK, Frodo set to 0</span></span><br></pre></td></tr></table></figure><p>枚举用某种底层整型类型表示，在C++98中，如何选择取决于实现，因此包含枚举的结构的长度可能随系统而异。对于作用域内枚举，C++11消除了这种依赖性。默认情况下，C++11作用域内枚举的底层类型为int。另外，还提供了一种语法，可用于做出不同的选择：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// underlying type for pizza is short</span></span><br><span class="line"><span class="class"><span class="keyword">enum</span> <span class="keyword">class</span> :</span> <span class="keyword">short</span> pizza &#123; Small, Medium, Large, XLarge &#125;;</span><br></pre></td></tr></table></figure><p><code>: short</code>将底层类型指定为short。在C++11中，也可使用这种语法来指定常规枚举类型的底层类型。</p><h3 id="10-7-抽象数据类型"><a href="#10-7-抽象数据类型" class="headerlink" title="10.7 抽象数据类型"></a>10.7 抽象数据类型</h3><p>抽象数据类型(abstract data type, ADT)，以通用的方式描述数据类型，而没有引入语言或实现细节，是一种非常友好的方式。</p><h2 id="第11章-使用类"><a href="#第11章-使用类" class="headerlink" title="第11章 使用类"></a>第11章 使用类</h2><h3 id="11-1-运算符重载"><a href="#11-1-运算符重载" class="headerlink" title="11.1 运算符重载"></a>11.1 运算符重载</h3><p>一个简单的例子：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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 class="comment">// mytime1.h</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> once</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifndef</span> MYTIME0_H_</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> MYTIME0_H_</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Time</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">Time</span>();</span><br><span class="line">    <span class="built_in">Time</span>(<span class="keyword">int</span> h, <span class="keyword">int</span> m = <span class="number">0</span>);</span><br><span class="line">    ...</span><br><span class="line">    Time <span class="keyword">operator</span>+(<span class="keyword">const</span> Time &amp;t) <span class="keyword">const</span>;</span><br><span class="line">    ...</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="keyword">int</span> minutes;</span><br><span class="line">    <span class="keyword">int</span> hours;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// mytime1.cpp</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;mytime1.h&quot;</span></span></span><br><span class="line"></span><br><span class="line">...</span><br><span class="line"></span><br><span class="line">Time Time::<span class="keyword">operator</span>+(<span class="keyword">const</span> Time &amp;t) <span class="keyword">const</span></span><br><span class="line">&#123;</span><br><span class="line">    Time sum;</span><br><span class="line">    sum.minutes = minutes + t.minutes;</span><br><span class="line">    sum.hours = hours + t.hours + sum.minutes / <span class="number">60</span>;</span><br><span class="line">    sum.minutes %= <span class="number">60</span>;</span><br><span class="line">    <span class="keyword">return</span> sum;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>经过上面的函数声明和函数定义，可以对Time对象使用<code>+</code>运算符：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">Time <span class="title">coding</span><span class="params">(<span class="number">2</span>, <span class="number">40</span>)</span></span>;</span><br><span class="line"><span class="function">Time <span class="title">fixing</span><span class="params">(<span class="number">5</span>, <span class="number">55</span>)</span></span>;</span><br><span class="line">Time total;</span><br><span class="line">total = coding + fixing;</span><br></pre></td></tr></table></figure><p>注意一个点：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Time t1, t2, t3, t4;</span><br><span class="line">t4 = t1 + t2 + t3;</span><br></pre></td></tr></table></figure><p><code>t4 = t1 + t2 + t3;</code>等价于<code>t4 = t1.operator+(t2 + t3);</code>，进而等价于<code>t4 = t1.operator+(t2.operator+(t3));</code>。</p><h4 id="1-11-1-重载限制"><a href="#1-11-1-重载限制" class="headerlink" title="1.11.1 重载限制"></a>1.11.1 重载限制</h4><p>多数C++运算符都可以使用上述的方式重载。重载的运算符不必是成员函数（有些情况例外），但必须至少有一个操作数是用于定义的类型。</p><ul><li>重载后的运算符至少有一个操作数是用户定义的类型，这将防止用户为标准类型重载运算符。</li><li>使用运算符时不能违反运算符原来的句法规则，也不能修改运算符的优先级。</li><li>不能创建新的运算符。</li><li>不能重载下面的运算符：<ul><li>sizeof</li><li><code>.</code>：成员运算符</li><li><code>.*</code>：成员指针运算符</li><li><code>::</code>：作用域解析运算符</li><li><code>?:</code>：条件运算符</li><li>typeid：一个RTTI运算符</li><li>const_cast</li><li>dynamic_cast</li><li>reinterpret_cast</li><li>static_cast</li></ul></li><li>表11.1中大多数运算符都可以通过成员或非成员函数进行重载，但下面的运算符只能通过成员函数进行重载<ul><li><code>=</code></li><li><code>()</code></li><li><code>[]</code></li><li><code>-&gt;</code></li></ul></li></ul><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210412125511819.png" alt="image-20210412125511819"></p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210412125522863.png" alt="image-20210412125522863"></p><h3 id="11-2-友元"><a href="#11-2-友元" class="headerlink" title="11.2 友元"></a>11.2 友元</h3><p>通常，只能通过类的公有方法访问类对象的私有部分，友元提供了访问类的私有成员的新方式。友元有3种：</p><ul><li>友元函数</li><li>友元类</li><li>友元成员函数</li></ul><p>可以通过让函数成为类的友元，可以赋予该函数与类的成员函数相同的访问权限。这里先介绍友元函数。</p><p>友元函数的应用场景大多在与二元运算符重载。如果使用成员函数进行运算符重载，那么只能是对象在前，如：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Time</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    ...</span><br><span class="line">    Time <span class="keyword">operator</span>*(<span class="keyword">double</span> n);</span><br><span class="line">    ...</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">A = B * <span class="number">2.75</span>;</span><br></pre></td></tr></table></figure><p>上面的语句将被转换成下面的成员函数调用：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">A = B.<span class="keyword">operator</span>*(<span class="number">2.75</span>);</span><br></pre></td></tr></table></figure><p>但是，下面这样写就不行了：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">A = <span class="number">2.75</span> * B;</span><br></pre></td></tr></table></figure><p>如此只能使用非成员函数来重载运算符，但是非成员函数无法访问类对象的私有成员，因此需要使用友元函数。</p><ul><li><p>创建友元函数的第一步是将函数原型放在类声明中，并在原型前加上关键字<code>friend</code>：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Time</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    ...</span><br><span class="line"><span class="keyword">friend</span> Time <span class="keyword">operator</span>*(<span class="keyword">double</span> n, <span class="keyword">const</span> Time &amp; t);</span><br><span class="line">    ...</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>虽然<code>operator*</code>函数是在类中声明的，但它不是成员函数，因此不能使用成员运算符来调用。然而，虽然不是成员函数，但是访问权限同成员函数。</p></li><li><p>第二步是编写函数定义，由于它不是成员函数，所以不要使用类作用域限定符</p></li></ul><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210412195311312.png" alt="image-20210412195311312"></p><p>实际上，也可以使用非友元函数调用成员函数：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Time <span class="keyword">operator</span>*(<span class="keyword">double</span> m, <span class="keyword">const</span> Time &amp;t)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">return</span> t * m; <span class="comment">// use t.operator*(m);</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="11-2-1-常用的友元：重载-lt-lt-运算符"><a href="#11-2-1-常用的友元：重载-lt-lt-运算符" class="headerlink" title="11.2.1 常用的友元：重载&lt;&lt;运算符"></a>11.2.1 常用的友元：重载<code>&lt;&lt;</code>运算符</h4><p>重载<code>&lt;&lt;</code>运算符可直接使用cout作用于类对象以实现对类的信息的输出。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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="class"><span class="keyword">class</span> <span class="title">Time</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    ...</span><br><span class="line">    <span class="keyword">friend</span> ostream &amp;<span class="keyword">operator</span>&lt;&lt;(ostream &amp;os, <span class="keyword">const</span> Time &amp;t);</span><br><span class="line">    ...</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">ostream &amp;<span class="keyword">operator</span>&lt;&lt;(ostream &amp;os, <span class="keyword">const</span> Time &amp;t)</span><br><span class="line">&#123;</span><br><span class="line">    os &lt;&lt; t.hours &lt;&lt; <span class="string">&quot; hours, &quot;</span> &lt;&lt; t.minutes &lt;&lt; <span class="string">&quot; minutes&quot;</span>;</span><br><span class="line">    <span class="keyword">return</span> os;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="11-3-重载运算符：作为成员函数还是非成员函数"><a href="#11-3-重载运算符：作为成员函数还是非成员函数" class="headerlink" title="11.3 重载运算符：作为成员函数还是非成员函数"></a>11.3 重载运算符：作为成员函数还是非成员函数</h3><p>非成员函数版本的重载运算符函数所需的形参数目与运算符使用的操作数目相同；而成员版本所需的参数数目少一个，因为其中的一个操作数是被隐式地传递的调用对象（this指针）。</p><h4 id="11-3-1-随机数"><a href="#11-3-1-随机数" class="headerlink" title="11.3.1 随机数"></a>11.3.1 随机数</h4><p>标准ANSI C库（C++也有）中有一个rand()函数，它返回一个从0到某个值（取决于实现）之间的随机整数。rand()函数将一种算法用于一个初始种子值来获得随机数，该随机值将用作下一个函数调用的种子，依此类推。srand()函数允许覆盖默认的种子值，重新启动另一个随机数序列。time(0)返回当前时间，通常为从1970年1月1日0点到当前时间的描述，返回值类型为<code>time_t</code>。使用<code>srand(time(0))</code>在每次程序运行时，都设置不同的种子。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;cstdlib&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;ctime&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">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="built_in">srand</span>(<span class="built_in">time</span>(<span class="number">0</span>));</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">10</span>; i++)</span><br><span class="line">        std::cout &lt;&lt; <span class="built_in">rand</span>() % <span class="number">100</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><h3 id="11-4-类的自动转换和强制类型转换"><a href="#11-4-类的自动转换和强制类型转换" class="headerlink" title="11.4 类的自动转换和强制类型转换"></a>11.4 类的自动转换和强制类型转换</h3><p>能接受一个参数的构造函数可以作为转换函数，为将类型与该参数类型相同的值转换为类提供了蓝图。可以使用<code>explicit</code>关键字关闭这种自动类型转换，但仍然允许强制类型转换。</p><blockquote><p>只接受一个参数的构造函数定义了从参数类型到类类型的转换。如果使用关键字explicit限定了这种构造函数，则它只能用于显示转换，否则也可以用于隐式转换。</p></blockquote><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Stonewt</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    ...</span><br><span class="line">    <span class="built_in">Stonewt</span>(<span class="keyword">double</span> lbs);</span><br><span class="line">    ...</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><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">Stonewt st = <span class="number">30.0</span>;  <span class="comment">// the same as Stonewt st(30.0);</span></span><br></pre></td></tr></table></figure><p>在不使用explicit关闭自动类型转换时，将在以下时刻执行参数类型到类类型自动类型转换：</p><ul><li><p>将类类型对象初始化为参数类型值时</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">Stonewt st = <span class="number">30.0</span>;  <span class="comment">// the same as Stonewt st(30.0);</span></span><br></pre></td></tr></table></figure></li><li><p>将参数类型值赋值给类类型对象时</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">st = <span class="number">25.3</span>;  <span class="comment">// use Stonewt(25.3) to create a temporary object, then call operator =</span></span><br></pre></td></tr></table></figure></li><li><p>将参数类型值传递给接受类类型参数的函数时</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">func</span><span class="params">(Stonewt st)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">func</span>(<span class="number">25.3</span>);</span><br></pre></td></tr></table></figure></li><li><p>返回值被声明为类类型的函数试图返回参数类型值时</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">Stonewt <span class="title">func2</span><span class="params">(<span class="keyword">double</span> n)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">double</span> temp;</span><br><span class="line">    ...</span><br><span class="line">    <span class="keyword">return</span> temp;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>在上述任意一种情况下，使用可转换为参数类型的内置类型时</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Stonewt s1 = <span class="number">30</span>;</span><br><span class="line">s1 = <span class="number">4</span>;</span><br><span class="line"><span class="built_in">func</span>(<span class="number">3</span>);</span><br></pre></td></tr></table></figure><p>这3条语句将先将int类型转换为double类型，再调用Stonewt(double)构造函数。然而，当且仅当转换不存在二义性时，才会进行这种二步转换。也就是说，如果这个类还定义了构造函数Stonest(long)，则编译器将拒绝这些语句，因为int可被转换为long或double，因此调用存在二义性。</p></li></ul><h4 id="11-4-1-转换函数"><a href="#11-4-1-转换函数" class="headerlink" title="11.4.1 转换函数"></a>11.4.1 转换函数</h4><p>上面介绍了从参数类型到类类型的自动类型转换，那么是否可以执行相反的转换？可以，需要在类中定义<strong>转换函数</strong>。转换函数的原型如下：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">operator</span> <span class="title">typeName</span><span class="params">()</span></span>;</span><br></pre></td></tr></table></figure><p>注意点：</p><ul><li>转换函数必须是类方法</li><li>转换函数不能指定返回类型</li><li>转换函数不能有参数</li></ul><p>typeName指出了要转换成的类型，因此不需要指定返回类型。转换函数是类方法，意味着它需要通过对象来调用，从而告知函数要转换的值。因此，函数不需要参数。对转换函数的定义使用类作用域解析运算符时，是写在operator前：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><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="function">className::<span class="keyword">operator</span> <span class="title">typeName</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果定义多个转换函数，在cout时如果不显示写出类型转换，将会出现二义性。只定义一个转换函数则不会。赋值情况也是如此，如果定义了对double和int的转换函数，那么赋给long类型将会出现二义性。</p><p>同样，转换函数在C++11中可对转换函数的原型使用explicit关键字取消自动类型转换。</p><h4 id="11-4-2-转换函数和友元函数"><a href="#11-4-2-转换函数和友元函数" class="headerlink" title="11.4.2 转换函数和友元函数"></a>11.4.2 转换函数和友元函数</h4><p>一个简单的例子解释二义性：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// stonewt.h</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> once</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifndef</span> __STONEWT_H__</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> __STONEWT_H__</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Stonewt</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="class"><span class="keyword">enum</span> &#123;</span> LBS_PER_STN = <span class="number">14</span> &#125;;</span><br><span class="line">    <span class="keyword">int</span> stone;</span><br><span class="line">    <span class="keyword">double</span> pds_left;</span><br><span class="line">    <span class="keyword">double</span> pounds;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">Stonewt</span>() &#123; stone = pds_left = pounds = <span class="number">0</span>; &#125;</span><br><span class="line">    <span class="built_in">Stonewt</span>(<span class="keyword">int</span> stn, <span class="keyword">double</span> lbs);</span><br><span class="line">    <span class="built_in">Stonewt</span>(<span class="keyword">double</span> lbs);</span><br><span class="line">    ~<span class="built_in">Stonewt</span>() &#123;&#125;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">show_lbs</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">show_stn</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// conversion functions</span></span><br><span class="line">    <span class="comment">// operator int();</span></span><br><span class="line">    <span class="function"><span class="keyword">operator</span> <span class="title">double</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 运算符重载</span></span><br><span class="line">    <span class="comment">// Stonewt operator+(const Stonewt &amp;st) const;  // 成员函数</span></span><br><span class="line">    <span class="keyword">friend</span> Stonewt <span class="keyword">operator</span>+(<span class="keyword">const</span> Stonewt &amp;st1, <span class="keyword">const</span> Stonewt &amp;st2);  <span class="comment">// 友元函数</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// stonewt.cpp</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;stonewt.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> std::cout;</span><br><span class="line"></span><br><span class="line">Stonewt::<span class="built_in">Stonewt</span>(<span class="keyword">int</span> stn, <span class="keyword">double</span> lbs)</span><br><span class="line">&#123;</span><br><span class="line">    stone = stn;</span><br><span class="line">    pds_left = lbs;</span><br><span class="line">    pounds = stone * LBS_PER_STN + pds_left;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">Stonewt::<span class="built_in">Stonewt</span>(<span class="keyword">double</span> lbs)</span><br><span class="line">&#123;</span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;call constructor with one argument\n&quot;</span>;</span><br><span class="line">    stone = <span class="built_in"><span class="keyword">int</span></span>(lbs) / LBS_PER_STN;</span><br><span class="line">    pds_left = <span class="built_in"><span class="keyword">int</span></span>(lbs) % LBS_PER_STN + lbs - <span class="built_in"><span class="keyword">int</span></span>(lbs);</span><br><span class="line">    pounds = lbs;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Stonewt::show_lbs</span><span class="params">()</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    cout &lt;&lt; pounds &lt;&lt; <span class="string">&quot; pounds\n&quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Stonewt::show_stn</span><span class="params">()</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    cout &lt;&lt; stone &lt;&lt; <span class="string">&quot; stones, &quot;</span> &lt;&lt; pds_left &lt;&lt; <span class="string">&quot; pounds\n&quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">Stonewt::<span class="keyword">operator</span> <span class="title">int</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> <span class="built_in"><span class="keyword">int</span></span>(pounds + <span class="number">0.5</span>); <span class="comment">// 四舍五入</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">Stonewt::<span class="keyword">operator</span> <span class="title">double</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> pounds;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Stonewt Stonewt::operator+(const Stonewt &amp;st) const</span></span><br><span class="line"><span class="comment">// &#123;</span></span><br><span class="line"><span class="comment">//     double pds = pounds + st.pounds;</span></span><br><span class="line"><span class="comment">//     // Stonewt sum(pds);</span></span><br><span class="line"><span class="comment">//     // return sum;</span></span><br><span class="line"><span class="comment">//     return pds;</span></span><br><span class="line"><span class="comment">// &#125;</span></span><br><span class="line"></span><br><span class="line">Stonewt <span class="keyword">operator</span>+(<span class="keyword">const</span> Stonewt &amp;st1, <span class="keyword">const</span> Stonewt &amp;st2)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">double</span> pds = st1.pounds + st2.pounds;</span><br><span class="line">    <span class="keyword">return</span> pds;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// main.cpp</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;stonewt.h&quot;</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="keyword">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">    Stonewt st1 = <span class="number">30.0</span>;</span><br><span class="line">    Stonewt st2 = <span class="number">30.0</span>;</span><br><span class="line">    Stonewt sum = <span class="number">20.0</span> + <span class="number">30.0</span>;  <span class="comment">// 生成临时对象，并调用拷贝构造函数</span></span><br><span class="line">    Stonewt sum2 = <span class="number">20.0</span> + st1;  <span class="comment">// compile error</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>上面的代码会发生编译错误，原因在于main函数中sum2所在行代码。这里，有两种解释：</p><ul><li>编译器检查是否对<code>+</code>做了运算符重载，代码中使用友元函数对<code>+</code>运算符进行了重载，于是看类型，20.0为double类型，而友元函数的第一个参数为const Stonewt类型的引用，于是检查是否定义了接受一个double类型参数的构造函数，确实定义了，因此将20.0通过该构造函数生成一个Stonewt类型的对象，进而执行对Stonewt类型的加法。</li><li>由于类中定义了特殊的成员函数——转换函数，因此st1将转换为double类型，执行对double类型的加法。</li></ul><p>于是乎，存在二义性，编译报错。</p><blockquote><p>应谨慎地使用隐式转换函数。通常，最好选择仅在被显示调用时才会执行的函数。</p></blockquote><h2 id="第12章-类和动态内存分配"><a href="#第12章-类和动态内存分配" class="headerlink" title="第12章 类和动态内存分配"></a>第12章 类和动态内存分配</h2><h3 id="12-1-动态内存和类"><a href="#12-1-动态内存和类" class="headerlink" title="12.1 动态内存和类"></a>12.1 动态内存和类</h3><h4 id="12-1-1-类的静态成员"><a href="#12-1-1-类的静态成员" class="headerlink" title="12.1.1 类的静态成员"></a>12.1.1 类的静态成员</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// stringbad.h</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">StringBad</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">int</span> num_strings;</span><br><span class="line">    ...</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// strinbad.cpp</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;stringbad.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> StringBad::num_strings = <span class="number">0</span>;</span><br></pre></td></tr></table></figure><p>静态数据成员在类声明中声明，在包含类方法的文件中初始化。初始化时使用作用域运算符来指出静态成员所属的类，且不需要加static关键字。但如果静态成员是const整型或枚举类型，则可以在类声明中初始化。</p><p>至于为什么不能在类声明中初始化静态成员变量，这是因为声明描述了如何分配内存，但并不分配内存。对于静态类成员，可以在类声明之外使用单独的语句来进行初始化，这是因为静态类成员是单独存储的，而不是对象的组成部分。</p><p>类的静态成员为类所有，为所有对象共用，只有一份数据拷贝。</p><blockquote><p>在构造函数中使用new类分配内存时，必须在相应的析构函数中使用delete释放内存。如果使用new[]来分配内存，则应使用delete[]来释放内存。</p></blockquote><h4 id="12-1-2-特殊成员函数"><a href="#12-1-2-特殊成员函数" class="headerlink" title="12.1.2 特殊成员函数"></a>12.1.2 特殊成员函数</h4><p>C++自动提供了下面这些成员函数：</p><ul><li>默认构造函数，如果没有定义构造函数</li><li>默认析构函数，如果没有定义</li><li>复制构造函数，如果没有定义</li><li>赋值运算符，如果没有定义</li><li>地址运算符，如果没有定义</li></ul><p>更准确地说，编译器将生成上述最后三个函数的定义——如果程序使用对象的方式要求这样做。例如，如果您将一个对象赋给另一个对象，编译器将提供赋值运算符的定义。</p><p>隐式地址运算符返回调用对象的地址(即this指针的值)。</p><p>C++11提供了另外两个特殊成员函数：</p><ul><li>移动构造函数</li><li>移动赋值运算符</li></ul><h5 id="1-默认构造函数"><a href="#1-默认构造函数" class="headerlink" title="1 - 默认构造函数"></a>1 - 默认构造函数</h5><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ClassName</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">...</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">ClassName</span>() &#123;&#125;</span><br><span class="line">    ...</span><br><span class="line">...</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>如果定义了构造函数，C++将不会定义默认构造函数。如果希望在创建对象时不显示地对它进行初始化，必须显示定义默认构造函数。</p><p>带参数的构造函数也可以是默认构造函数，只要所有参数都有默认值。但不能同时定义一个无参构造函数和所有参数都有默认值的构造函数，这会导致二义性。</p><h5 id="2-复制构造函数"><a href="#2-复制构造函数" class="headerlink" title="2 - 复制构造函数"></a>2 - 复制构造函数</h5><p>复制构造函数用于将一个对象复制到新创建的对象中。也就是说，它用于初始化过程（包括按值传递参数），而不是常规的赋值过程。类的复制构造函数原型通常如下：</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">ClassName</span>(<span class="keyword">const</span> ClassName &amp;);</span><br></pre></td></tr></table></figure><p>对于复制构造函数，需要明确两点：何时调用、有何功能。</p><p>何时调用：</p><p>新建一个对象并将其初始化为同类现有对象时、按值传递函数参数时、返回对象时以及编译器生成临时对象时，复制构造函数都将被调用：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">StringBad <span class="title">ditto</span><span class="params">(motto)</span></span>;</span><br><span class="line">StringBad metoo = motto;</span><br><span class="line">StringBad also = <span class="built_in">StringBad</span>(motto);</span><br><span class="line">String *psb = <span class="keyword">new</span> <span class="built_in">StringBad</span>(motto);</span><br></pre></td></tr></table></figure><p>中间两种可能会使用复制构造函数直接创建metoo和also，也可能先使用复制构造函数生成临时对象，再调用赋值运算符，这两种情况将取决于具体的实现，通常现在的编译器采取第一种做法。</p><p>有何功能：</p><p>默认的复制构造函数逐个复制非静态成员，复制的是成员的值，为浅复制。如果成员本身就是类对象，则使用这个类的复制构造函数来复制成员对象。静态成员不受影响，因为它们属于整个类，而不是某个对象。</p><blockquote><p>如果类中包含这样的静态数据成员，即其值将再新对象被创建时发生变化，则应该提供一个显式复制构造函数来处理计数问题。</p><p>如果类中包含了使用new初始化的指针成员，应当定义一个复制构造函数，以复制指向的数据，而不是指针，这被称为深度复制。复制的另一种形式（成员复制或浅复制）只是复制指针值。浅复制仅浅浅地复制指针信息，而不会深入“挖掘”以复制指针引用的结构。</p></blockquote><h5 id="3-赋值运算符"><a href="#3-赋值运算符" class="headerlink" title="3 - 赋值运算符"></a>3 - 赋值运算符</h5><p>ASCI C允许结构赋值，而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">ClassName &amp;<span class="keyword">operator</span>=(<span class="keyword">const</span> ClassName &amp;);</span><br></pre></td></tr></table></figure><p>它接受并返回一个指向类对象的引用。</p><p>赋值运算符的功能是将已有对象赋给另一个对象，即将已有的对象赋给另一个对象时，将使用重载的赋值运算符：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">StringBad knot;</span><br><span class="line">knot = headline1;</span><br><span class="line">knot = <span class="built_in">StringBad</span>(...);</span><br></pre></td></tr></table></figure><p>初始化对象时，并不一定会使用赋值运算符。</p><p>与复制构造函数相似，赋值运算符的隐式实现也对成员进行逐个赋值，如果成员本身就是类对象，则程序将使用为这个类定义的赋值运算符来赋值该成员，但静态数据成员不受影响。当然，默认的赋值运算符重载也是浅复制。</p><h3 id="12-2-静态类成员函数、更多的运算符重载"><a href="#12-2-静态类成员函数、更多的运算符重载" class="headerlink" title="12.2 静态类成员函数、更多的运算符重载"></a>12.2 静态类成员函数、更多的运算符重载</h3><h4 id="12-2-1-静态类成员函数"><a href="#12-2-1-静态类成员函数" class="headerlink" title="12.2.1 静态类成员函数"></a>12.2.1 静态类成员函数</h4><p>可以将成员函数声明为静态的，函数声明必须包含关键字static，如果函数定义是独立的，则其中不能包含关键字static。</p><p>不能通过对象调用静态成员函数。实际上，静态成员函数甚至不能使用this指针。如果静态成员函数是在公有部分声明的，则可以使用类名和作用域解析运算符来调用它。</p><p>由于静态成员函数不与特定的对象关联，因此只能使用静态数据成员。</p><h4 id="12-2-2-重载"><a href="#12-2-2-重载" class="headerlink" title="12.2.2 重载[]"></a>12.2.2 重载<code>[]</code></h4><p>可以对<code>[]</code>运算符进行重载，使得类对象能够像访问数组元素一样使用下标访问自身的数据。注意两种形式：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">// String.h</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">String</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="keyword">char</span> *str;</span><br><span class="line">    <span class="keyword">int</span> len;</span><br><span class="line">    ...</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    ...</span><br><span class="line">    <span class="keyword">char</span> &amp;<span class="keyword">operator</span>[](<span class="keyword">int</span> index); <span class="comment">// 1</span></span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">char</span> &amp;opeartor[](<span class="keyword">int</span> index) <span class="keyword">const</span>; <span class="comment">// 2</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// String.cpp</span></span><br><span class="line"><span class="keyword">char</span> &amp;String::<span class="keyword">operator</span>[](<span class="keyword">int</span> index)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">return</span> str[index];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="keyword">char</span> &amp;String::<span class="keyword">operator</span>[](<span class="keyword">int</span> index) <span class="keyword">const</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">return</span> str[index];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>方式1的重载可以使得String类的对象可以像数组一样使用下标访问和修改数据。而方式二可以使得const String类对象使用下标访问数据，但是不能修改数据。</p><h4 id="12-2-3-进一步重载赋值运算符"><a href="#12-2-3-进一步重载赋值运算符" class="headerlink" title="12.2.3 进一步重载赋值运算符"></a>12.2.3 进一步重载赋值运算符</h4><p>对于String类来讲，如果经常使用<code>st = &quot;hello&quot;;</code>这种语句，调用单参数构造函数生成临时对象，再调用赋值运算符重载进行拷贝，则不如定义一个针对<code>const char*</code>类型的赋值运算符重载：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><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">String &amp;<span class="keyword">operator</span>=(<span class="keyword">const</span> <span class="keyword">char</span> *s);</span><br><span class="line"></span><br><span class="line">String &amp;String::<span class="keyword">operator</span>=(<span class="keyword">const</span> <span class="keyword">char</span> *s)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">delete</span> [] str;</span><br><span class="line">    str = null;</span><br><span class="line">    len = <span class="built_in">strlen</span>(s);</span><br><span class="line">    str = <span class="keyword">new</span> <span class="keyword">char</span>[len + <span class="number">1</span>];</span><br><span class="line">    <span class="built_in">strcpy</span>(str, s);</span><br><span class="line">    <span class="keyword">return</span> *<span class="keyword">this</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样一来，可以省去调用一次构造函数生成临时对象，提高执行效率。</p><h3 id="12-3-使用指向对象的指针"><a href="#12-3-使用指向对象的指针" class="headerlink" title="12.3 使用指向对象的指针"></a>12.3 使用指向对象的指针</h3><h4 id="12-3-1-析构函数调用时机"><a href="#12-3-1-析构函数调用时机" class="headerlink" title="12.3.1 析构函数调用时机"></a>12.3.1 析构函数调用时机</h4><p>在下述情况下析构函数将被调用：</p><ul><li>如果对象是自动变量，当执行完定义该对象的程序块时，将调用该对象的析构函数。</li><li>如果对象是静态变量，则在程序结束时将调用对象的析构函数。</li><li>如果对象是用new创建的，则仅当显示使用delete删除对象时，其析构函数才会被调用。</li></ul><h4 id="12-3-2-指针和对象小结"><a href="#12-3-2-指针和对象小结" class="headerlink" title="12.3.2 指针和对象小结"></a>12.3.2 指针和对象小结</h4><ul><li><p>使用常规表示法来声明指向对象的指针：</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">String *glamour;</span><br></pre></td></tr></table></figure></li><li><p>可以将指针初始化为指向已有对象：</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">String *first = &amp;sayings[<span class="number">0</span>];</span><br></pre></td></tr></table></figure></li><li><p>可以使用new来初始化指针，这将创建一个对象：</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">String *favorite = <span class="keyword">new</span> <span class="built_in">String</span>(sayings[choice]);</span><br></pre></td></tr></table></figure></li><li><p>对类使用new将调用相应的类构造函数来初始化新创建的对象：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">String *gleep = <span class="keyword">new</span> String;             <span class="comment">// 调用默认构造函数</span></span><br><span class="line">String *glop = <span class="keyword">new</span> <span class="built_in">String</span>(<span class="string">&quot;my my my&quot;</span>);  <span class="comment">// 调用String(const char *)构造函数</span></span><br></pre></td></tr></table></figure></li><li><p>可以使用<code>-&gt;</code>运算符通过指针访问类方法：</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="keyword">if</span> (sayings[i].<span class="built_in">length</span>() &lt; shortest-&gt;<span class="built_in">length</span>())</span><br></pre></td></tr></table></figure></li><li><p>可以对对象指针应用解除引用运算符<code>*</code>来获得对象：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (sayings[i] &lt; *first)</span><br><span class="line">    first = &amp;sayings[i];</span><br></pre></td></tr></table></figure></li></ul><h4 id="12-3-3-再谈定位new运算符"><a href="#12-3-3-再谈定位new运算符" class="headerlink" title="12.3.3 再谈定位new运算符"></a>12.3.3 再谈定位new运算符</h4><p>delete可与new运算符配套使用，但不能与定位new运算符配套使用。如果将对象使用定位new运算符创建在了某段内存上，将不能使用delete来释放它。如此，对象得不到释放，无法调用析构函数。因此，使用定位new运算符创建的对象需要显示调用析构函数，这是显示调用析构函数的一种情况。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// placenew1.cpp -- new, placement new, no delete</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;new&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-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">const</span> <span class="keyword">int</span> BUF = <span class="number">512</span>;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">JustTesting</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    string words;</span><br><span class="line">    <span class="keyword">int</span> number;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">JustTesting</span>(<span class="keyword">const</span> string &amp;s = <span class="string">&quot;Just Testing&quot;</span>, <span class="keyword">int</span> n = <span class="number">0</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        words = s;</span><br><span class="line">        number = n;</span><br><span class="line">        cout &lt;&lt; words &lt;&lt; <span class="string">&quot; constructed\n&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    ~<span class="built_in">JustTesting</span>() &#123; cout &lt;&lt; words &lt;&lt; <span class="string">&quot; destroyed\n&quot;</span>; &#125;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">Show</span><span class="params">()</span> <span class="keyword">const</span> </span>&#123; cout &lt;&lt; words &lt;&lt; <span class="string">&quot;, &quot;</span> &lt;&lt; number &lt;&lt; endl; &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">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="keyword">char</span> *buffer = <span class="keyword">new</span> <span class="keyword">char</span>[BUF]; <span class="comment">// get a block of memory in heap</span></span><br><span class="line"></span><br><span class="line">    JustTesting *pc1, *pc2;</span><br><span class="line"></span><br><span class="line">    pc1 = <span class="built_in"><span class="keyword">new</span></span> (buffer) JustTesting; <span class="comment">// place object in buffer</span></span><br><span class="line">    pc2 = <span class="keyword">new</span> <span class="built_in">JustTesting</span>(<span class="string">&quot;Heap1&quot;</span>, <span class="number">20</span>);</span><br><span class="line"></span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Memory block addresses:\n&quot;</span></span><br><span class="line">         &lt;&lt; <span class="string">&quot;buffer: &quot;</span></span><br><span class="line">         &lt;&lt; (<span class="keyword">void</span> *)buffer &lt;&lt; <span class="string">&quot;   heap: &quot;</span> &lt;&lt; pc2 &lt;&lt; endl;</span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Memory contents:\n&quot;</span>;</span><br><span class="line">    cout &lt;&lt; pc1 &lt;&lt; <span class="string">&quot;: &quot;</span>;</span><br><span class="line">    pc1-&gt;<span class="built_in">Show</span>();</span><br><span class="line">    cout &lt;&lt; pc2 &lt;&lt; <span class="string">&quot;: &quot;</span>;</span><br><span class="line">    pc2-&gt;<span class="built_in">Show</span>();</span><br><span class="line"></span><br><span class="line">    JustTesting *pc3, *pc4;</span><br><span class="line">    pc3 = <span class="built_in"><span class="keyword">new</span></span> (buffer) <span class="built_in">JustTesting</span>(<span class="string">&quot;Bad Idea&quot;</span>, <span class="number">6</span>);</span><br><span class="line">    pc4 = <span class="keyword">new</span> <span class="built_in">JustTesting</span>(<span class="string">&quot;Heap2&quot;</span>, <span class="number">10</span>);</span><br><span class="line"></span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Memory contents:\n&quot;</span>;</span><br><span class="line">    cout &lt;&lt; pc3 &lt;&lt; <span class="string">&quot;: &quot;</span>;</span><br><span class="line">    pc3-&gt;<span class="built_in">Show</span>();</span><br><span class="line">    cout &lt;&lt; pc4 &lt;&lt; <span class="string">&quot;: &quot;</span>;</span><br><span class="line">    pc4-&gt;<span class="built_in">Show</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">delete</span> pc2;</span><br><span class="line">    <span class="keyword">delete</span> pc4;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">delete</span>[] buffer;</span><br><span class="line"></span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Done\n&quot;</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>上面的代码中，对pc3使用定位new运算符之前没有释放pc1指向的对象，因此，直接覆盖pc1所在的内存，程序运行后，pc1和pc3指向的对象都无法调用析构函数，下面是程序的运行结果：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">Just Testing constructed</span><br><span class="line">Heap1 constructed</span><br><span class="line">Memory block addresses:</span><br><span class="line">buffer: 0x6d1730   heap: 0x6d1940</span><br><span class="line">Memory contents:</span><br><span class="line">0x6d1730: Just Testing, 0</span><br><span class="line">0x6d1940: Heap1, 20</span><br><span class="line">Bad Idea constructed</span><br><span class="line">Heap2 constructed</span><br><span class="line">Memory contents:</span><br><span class="line">0x6d1730: Bad Idea, 6</span><br><span class="line">0x6d1970: Heap2, 10</span><br><span class="line">Heap1 destroyed</span><br><span class="line">Heap2 destroyed</span><br><span class="line">Done</span><br></pre></td></tr></table></figure><p>再看另一段代码：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// placenew1.cpp -- new, placement new, no delete</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;new&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-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">const</span> <span class="keyword">int</span> BUF = <span class="number">512</span>;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">JustTesting</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    string words;</span><br><span class="line">    <span class="keyword">int</span> number;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">JustTesting</span>(<span class="keyword">const</span> string &amp;s = <span class="string">&quot;Just Testing&quot;</span>, <span class="keyword">int</span> n = <span class="number">0</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        words = s;</span><br><span class="line">        number = n;</span><br><span class="line">        cout &lt;&lt; words &lt;&lt; <span class="string">&quot; constructed\n&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    ~<span class="built_in">JustTesting</span>() &#123; cout &lt;&lt; words &lt;&lt; <span class="string">&quot; destroyed\n&quot;</span>; &#125;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">Show</span><span class="params">()</span> <span class="keyword">const</span> </span>&#123; cout &lt;&lt; words &lt;&lt; <span class="string">&quot;, &quot;</span> &lt;&lt; number &lt;&lt; endl; &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">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="keyword">char</span> *buffer = <span class="keyword">new</span> <span class="keyword">char</span>[BUF]; <span class="comment">// get a block of memory in heap</span></span><br><span class="line"></span><br><span class="line">    JustTesting *pc1, *pc2;</span><br><span class="line"></span><br><span class="line">    pc1 = <span class="built_in"><span class="keyword">new</span></span> (buffer) JustTesting; <span class="comment">// place object in buffer</span></span><br><span class="line">    pc2 = <span class="keyword">new</span> <span class="built_in">JustTesting</span>(<span class="string">&quot;Heap1&quot;</span>, <span class="number">20</span>);</span><br><span class="line"></span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Memory block addresses:\n&quot;</span></span><br><span class="line">         &lt;&lt; <span class="string">&quot;buffer: &quot;</span></span><br><span class="line">         &lt;&lt; (<span class="keyword">void</span> *)buffer &lt;&lt; <span class="string">&quot;   heap: &quot;</span> &lt;&lt; pc2 &lt;&lt; endl;</span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Memory contents:\n&quot;</span>;</span><br><span class="line">    cout &lt;&lt; pc1 &lt;&lt; <span class="string">&quot;: &quot;</span>;</span><br><span class="line">    pc1-&gt;<span class="built_in">Show</span>();</span><br><span class="line">    cout &lt;&lt; pc2 &lt;&lt; <span class="string">&quot;: &quot;</span>;</span><br><span class="line">    pc2-&gt;<span class="built_in">Show</span>();</span><br><span class="line"></span><br><span class="line">    JustTesting *pc3, *pc4;</span><br><span class="line">    pc3 = <span class="built_in"><span class="keyword">new</span></span> (buffer + <span class="built_in"><span class="keyword">sizeof</span></span>(JustTesting)) <span class="built_in">JustTesting</span>(<span class="string">&quot;Better Idea&quot;</span>, <span class="number">6</span>);</span><br><span class="line">    pc4 = <span class="keyword">new</span> <span class="built_in">JustTesting</span>(<span class="string">&quot;Heap2&quot;</span>, <span class="number">10</span>);</span><br><span class="line"></span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Memory contents:\n&quot;</span>;</span><br><span class="line">    cout &lt;&lt; pc3 &lt;&lt; <span class="string">&quot;: &quot;</span>;</span><br><span class="line">    pc3-&gt;<span class="built_in">Show</span>();</span><br><span class="line">    cout &lt;&lt; pc4 &lt;&lt; <span class="string">&quot;: &quot;</span>;</span><br><span class="line">    pc4-&gt;<span class="built_in">Show</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">delete</span> pc2;</span><br><span class="line">    <span class="keyword">delete</span> pc4;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// explicitly destroy placement new objects</span></span><br><span class="line">    pc3-&gt;~<span class="built_in">JustTesting</span>();</span><br><span class="line">    pc1-&gt;~<span class="built_in">JustTesting</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">delete</span>[] buffer;</span><br><span class="line"></span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Done\n&quot;</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>这段代码的运行结果如下：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">Just Testing constructed</span><br><span class="line">Heap1 constructed</span><br><span class="line">Memory block addresses:</span><br><span class="line">buffer: 0x1081730   heap: 0x1081940</span><br><span class="line">Memory contents:</span><br><span class="line">0x1081730: Just Testing, 0</span><br><span class="line">0x1081940: Heap1, 20</span><br><span class="line">Better Idea constructed</span><br><span class="line">Heap2 constructed</span><br><span class="line">Memory contents:</span><br><span class="line">0x1081758: Better Idea, 6</span><br><span class="line">0x1081970: Heap2, 10</span><br><span class="line">Heap1 destroyed</span><br><span class="line">Heap2 destroyed</span><br><span class="line">Better Idea destroyed</span><br><span class="line">Just Testing destroyed</span><br><span class="line">Done</span><br></pre></td></tr></table></figure><p>所有对象都得到了释放，且buffer也被delete释放。这里需要注意的一点是，pc3位于pc1的后面，因此需要先释放，也就是说，释放的顺序与创建的顺序相反，原因在于晚创建的对象可能依赖于早创建的对象。另外，仅当所有对象都被销毁后，才能释放用于存储这些对象的缓冲区。</p><h3 id="12-4-成员初始化列表"><a href="#12-4-成员初始化列表" class="headerlink" title="12.4 成员初始化列表"></a>12.4 成员初始化列表</h3><p>成员初始化列表由逗号分隔的初始化列表组成，前面带冒号，它位于参数列表的右括号之后、函数体左括号之前。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Queue::<span class="built_in">Queue</span>(<span class="keyword">int</span> qs) : <span class="built_in">qsize</span>(qs)</span><br><span class="line">&#123;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>只有构造函数可以使用这种初始化列表语法</strong>。此外，对于类中声明的const变量，之前我们说无法对其进行初始化，要么使用枚举，要么加上static关键字来在类中声明常量。现在，可以使用初始化列表在执行构造函数的函数体之前对const变量进行初始化。上述代码的qsize就是一个const变量。而对于被声明为引用的类成员，也必须使用这种语法进行初始化。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210415111429511.png" alt="image-20210415111429511"></p><h3 id="12-5-C-11类内初始化"><a href="#12-5-C-11类内初始化" class="headerlink" title="12.5  C++11类内初始化"></a>12.5  C++11类内初始化</h3><p>C++11允许您以更直观的方式进行初始化：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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="class"><span class="keyword">class</span> <span class="title">Classy</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">    <span class="comment">// in-class initialization</span></span><br><span class="line"><span class="keyword">int</span> mem1 = <span class="number">10</span>;</span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">int</span> mem2 = <span class="number">20</span>;</span><br><span class="line">    ...</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>这与在构造函数中使用成员初始化列表等价：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Classy::<span class="built_in">Classy</span>() : <span class="built_in">mem1</span>(<span class="number">10</span>), <span class="built_in">mem2</span>(<span class="number">20</span>) &#123; ... &#125;</span><br></pre></td></tr></table></figure><p>除非调用了使用成员初始化列表的构造函数，在这种情况下，实际列表将覆盖这些默认初始值：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Classy::<span class="built_in">Classy</span>(<span class="keyword">int</span> n) : <span class="built_in">mem1</span>(n) &#123; ... &#125;</span><br></pre></td></tr></table></figure><p>在这里，构造函数将使用n来初始化mem1，但mem2仍被设置为20。</p><h2 id="第13章-类继承"><a href="#第13章-类继承" class="headerlink" title="第13章 类继承"></a>第13章 类继承</h2><h3 id="13-1-公有继承"><a href="#13-1-公有继承" class="headerlink" title="13.1 公有继承"></a>13.1 公有继承</h3><p>公有继承，基类的公有成员将成为派生类的公有成员，基类的私有部分也成为派生类的一部分，但只能通过基类的公有和保护方法访问。派生类的对象有以下特征：</p><ul><li>派生类对象存储了基类的数据成员（派生类继承了基类的的实现）</li><li>派生类对象可以使用基类的方法（派生类继承了基类的接口）</li></ul><h4 id="13-1-1-构造函数：访问权限的考虑"><a href="#13-1-1-构造函数：访问权限的考虑" class="headerlink" title="13.1.1 构造函数：访问权限的考虑"></a>13.1.1 构造函数：访问权限的考虑</h4><p>派生类不能直接访问基类的私有成员，而必须通过基类的方法进行访问。派生类构造函数必须使用基类构造函数。 创建派生类对象时，程序首先创建基类对象。从概念上说，这意味着基类对象应当在程序进入派生类构造函数之前被创建。C++使用成员初始化列表语法完成这项工作。</p><p>创建派生类对象时，程序首先调用基类的构造函数，然后再调用派生类构造函数。基类构造函数负责初始化继承的数据成员；派生类构造函数主要用于初始化新增的数据成员。<strong>派生类的构造函数总是调用一个基类构造函数</strong>。可以成员初始化列表指明要使用的构造函数，否则将使用默认的基类构造函数。派生类对象过期时，程序首先调用派生类析构函数，然后再调用基类析构函数。</p><h4 id="13-1-2-派生类与基类之间的特殊关系"><a href="#13-1-2-派生类与基类之间的特殊关系" class="headerlink" title="13.1.2 派生类与基类之间的特殊关系"></a>13.1.2 派生类与基类之间的特殊关系</h4><ul><li><p>派生类对象可以使用基类的方法，条件是方法不是私有的。</p></li><li><p>基类指针可以在不显示类型转换的情况下指向派生类对象；基类引用可以在不显示类型转换的情况下引用派生类对象。通常，C++要求引用和指针类型与赋给的类型匹配，但这一规则对继承来说是例外。然而，这种例外只是单向的，不可以将基类对象和地址赋给派生类引用和指针。利用这样的属性，可以带来一大好处：在函数参数中使用基类引用或指针，在调用时可以向其传递基类对象、地址或派生类的对象、地址，这就是多态的一种体现。</p></li></ul><h3 id="13-2-多态公有继承"><a href="#13-2-多态公有继承" class="headerlink" title="13.2 多态公有继承"></a>13.2 多态公有继承</h3><h4 id="13-2-1-virtual关键字和虚方法"><a href="#13-2-1-virtual关键字和虚方法" class="headerlink" title="13.2.1 virtual关键字和虚方法"></a>13.2.1 virtual关键字和虚方法</h4><p>如果要在派生类中重新定义基类的方法，通常应将基类方法声明为虚方法。这样，在使用引用或指针调用该方法时，将根据引用或指针指向的对象的类型来选择方法的版本，而不是根据引用或指针的类型来选择。如果声明为类型本身，而不是指针或引用，则按声明类型选择方法版本。</p><p>虚方法在基类中被声明为虚的后，它在派生类中将自动成为虚方法，尽管如此，在派生类声明中使用关键字virtual来指出哪些函数时虚函数也不失为一个好办法。</p><h4 id="13-2-2-虚析构函数"><a href="#13-2-2-虚析构函数" class="headerlink" title="13.2.2 虚析构函数"></a>13.2.2 虚析构函数</h4><p><strong>虚析构函数</strong>可确保释放派生对象时，按正确的顺序调用析构函数。如果有一个基类的指针指向派生类的对象，若析构函数不是虚函数，则按指针类型调用虚函数，即只调用基类的析构函数。如果派生类中有使用new分配的内存的数据成员，则派生类的析构函数无法调用，将无法释放该内存空间。使用虚析构函数，将根据指针或引用指向的对象的类型来选择函数版本，此时将会调用派生类的析构函数，然后自动调用基类的析构函数。因此，使用析构函数，可以保证正确的析构函数序列被调用。</p><h3 id="13-3-静态联编和动态联编"><a href="#13-3-静态联编和动态联编" class="headerlink" title="13.3 静态联编和动态联编"></a>13.3 静态联编和动态联编</h3><p>将源代码中的函数调用解释为执行特定的函数代码块被称为函数名联编（binding）。在编译过程中进行这种联编被称为<strong>静态联编</strong>，又称早期联编。然而C++引入虚函数，导致使用哪一个函数是不能在编译时确定的，因为编译器不知道用户将选择哪种类型的对象。编译器必须生成能够在运行时选择正确的虚函数的代码，这被称为<strong>动态联编</strong>，又称为晚期联编。</p><h4 id="13-3-1-指针和引用类型的兼容性"><a href="#13-3-1-指针和引用类型的兼容性" class="headerlink" title="13.3.1 指针和引用类型的兼容性"></a>13.3.1 指针和引用类型的兼容性</h4><p>在C++中，动态联编与通过指针和引用调用方法相关。通常C++不允许将一种类型的地址赋给另一种类型的指针，也不允许一种类型的引用指向另一种类型。然而，指向基类的引用或指针可以引用派生类对象，而不必进行强制类型转换。</p><p>将派生类引用或指针转换为基类引用或指针被称为向上强制转换（upcasting），这使公有继承不需要进行显示类型转换，该规则是is-a规则的一部分。</p><p>将基类指针或引用转换为派生类指针或引用——称为向下强制转换（downcasting）。如果不适用显示类型转换，则向下强制转换是不允许的。</p><h4 id="13-3-2-虚函数和动态联编"><a href="#13-3-2-虚函数和动态联编" class="headerlink" title="13.3.2 虚函数和动态联编"></a>13.3.2 虚函数和动态联编</h4><p>如果要在派生类中重新定义基类的方法，则将它设置为虚方法；否则，设置为非虚方法。</p><h5 id="1-虚函数工作原理"><a href="#1-虚函数工作原理" class="headerlink" title="1 - 虚函数工作原理"></a>1 - 虚函数工作原理</h5><p>编译器处理虚函数的方法是：给每个对象添加一个隐藏成员。隐藏成员中保存了一个指向函数地址数组的指针。这种数组称为<strong>虚函数表</strong>（virtual function table，tvbl）。虚函数表中存储了为类对象进行声明的虚函数的地址。例如，基类对象包含一个指针，该指针指向基类中所有虚函数的地址表。派生类对象将包含一个指向独立地址表的指针。如果派生类提供了虚函数的新定义，该虚函数表将保存新函数的地址；如果派生类没有重新定义虚函数，该虚函数表将保存函数原始版本的地址。如果派生类定义了新的虚函数，则该函数的地址也被添加到vtbl中。注意，无论时类中包含的虚函数是1个还是10个，都只需在对象中添加1个地址成员，只是表的大小不同而已。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210419165628479.png" alt="image-20210419165628479"></p><p>调用虚函数时，程序将查看存储在对象中的vtbl地址，然后转向相应的函数地址表。总之，使用虚函数时，在内存和执行速度方面有一定的成本，包括：</p><ul><li>每个对象都将增大，增大量为存储地址的空间；</li><li>对于每个类，编译器都创建一个虚函数地址表（数组）；</li><li>对于每个函数调用，都需要执行一项额外的操作，即到表中查找地址</li></ul><h5 id="2-有关虚函数注意事项"><a href="#2-有关虚函数注意事项" class="headerlink" title="2 - 有关虚函数注意事项"></a>2 - 有关虚函数注意事项</h5><ul><li><p>构造函数不能是虚函数。创建派生类对象时，将调用派生类的构造函数，而不是基类的构造函数，然后，派生类的构造函数将使用基类的一个构造函数，这种顺序不同于继承机制。因此，派生类不继承基类的构造函数，所以将类构造函数声明为虚的没什么意义。</p></li><li><p>析构函数应当是虚函数，除非不用做基类。通常应给基类提供一个虚析构函数，即使它并不需要析构函数。</p></li><li><p>友元不能是虚函数。友元不是类成员，而只有成员才能是虚函数。</p></li><li><p>如果派生类没有重新定义函数，将使用该函数的基类版本。如果派生类位于派生链中，则将使用最新的虚函数版本，例外的情况是基类版本是隐藏的。</p></li><li><p>重新定义将隐藏方法。如果在派生类中重新定义函数（改变了特征标，即参数列表），不会生成函数的两个重载版本，将不是使用相同的函数特征标覆盖基类声明，而是隐藏同名的基类方法，不管参数特征标如何。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Dwelling</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">showperks</span><span class="params">(<span class="keyword">int</span> a)</span> <span class="keyword">const</span></span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Hovel</span> :</span> <span class="keyword">public</span> Dwelling</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">showperks</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">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">    Hovel trump;</span><br><span class="line">    trump.<span class="built_in">showperks</span>();   <span class="comment">// valid</span></span><br><span class="line">    trump.<span class="built_in">showperks</span>(<span class="number">5</span>);  <span class="comment">// invalid</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>如果仅仅是改变了返回类型，则要看是否协变：</p><ul><li><p>如果重新定义继承的方法，应确保与原来的原型完全相同，但如果返回类型是基类引用或指针，则可以修改为指向派生类的引用或指针。这种特性称为返回类型协变。如果不满足协变规则，将编译报错。所以，如果不改变参数列表，则返回类型要么一样，要么符合协变。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Dwelling</span></span></span><br><span class="line"><span class="class">  &#123;</span></span><br><span class="line">  <span class="keyword">public</span>:</span><br><span class="line">      <span class="function"><span class="keyword">virtual</span> Dwelling &amp;<span class="title">build</span><span class="params">(<span class="keyword">int</span> n)</span></span>;</span><br><span class="line">  &#125;;</span><br><span class="line">  </span><br><span class="line">  <span class="class"><span class="keyword">class</span> <span class="title">Hovel</span> :</span> <span class="keyword">public</span> Dwelling</span><br><span class="line">  &#123;</span><br><span class="line">  <span class="keyword">public</span>:</span><br><span class="line">      <span class="function"><span class="keyword">virtual</span> Hovel &amp;<span class="title">build</span><span class="params">(<span class="keyword">int</span> n)</span></span>;  <span class="comment">// same function signature</span></span><br><span class="line">  &#125;;</span><br></pre></td></tr></table></figure></li></ul></li></ul><ul><li><p>如果基类声明被重载了，则应在派生类中重新定义所有的基类版本。如果只重新定义一个版本，则另外两个版本将被隐藏，派生类对象将无法使用它们。注意，如果不需要修改，则新定义只需调用基类版本即可。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Dwelling</span></span></span><br><span class="line"><span class="class">  &#123;</span></span><br><span class="line">  <span class="keyword">public</span>:</span><br><span class="line">      <span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">showperks</span><span class="params">(<span class="keyword">int</span> a)</span> <span class="keyword">const</span></span>;</span><br><span class="line">      <span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">showperks</span><span class="params">(<span class="keyword">double</span> x)</span> <span class="keyword">const</span></span>;</span><br><span class="line">      <span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">showperks</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line">  &#125;;</span><br><span class="line">  </span><br><span class="line">  <span class="class"><span class="keyword">class</span> <span class="title">Hovel</span> :</span> <span class="keyword">public</span> Dwelling</span><br><span class="line">  &#123;</span><br><span class="line">  <span class="keyword">public</span>:</span><br><span class="line">      <span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">showperks</span><span class="params">(<span class="keyword">int</span> a)</span> <span class="keyword">const</span></span>;</span><br><span class="line">      <span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">showperks</span><span class="params">(<span class="keyword">double</span> x)</span> <span class="keyword">const</span></span>;</span><br><span class="line">      <span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">showperks</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line">  &#125;;</span><br></pre></td></tr></table></figure></li></ul><h3 id="13-4-访问控制：protected"><a href="#13-4-访问控制：protected" class="headerlink" title="13.4 访问控制：protected"></a>13.4 访问控制：protected</h3><p>protected成员对类外不可见，对类内可见，这一点与private是一致的。protected的特点在继承时体现：protected成员在派生类中可见。</p><h3 id="13-5-抽象基类"><a href="#13-5-抽象基类" class="headerlink" title="13.5 抽象基类"></a>13.5 抽象基类</h3><p>C++通过使用纯虚函数（pure virtual function）提供未实现的函数。纯虚函数声明的结尾处为=0。当类声明中包含纯虚函数时，则不能创建该类的对象。包含纯虚函数的类只用作基类。要成为真正的抽象基类，必须至少包含一个纯虚函数。<strong>C++允许纯虚函数有定义</strong>。</p><h3 id="13-6-继承和动态内存分配"><a href="#13-6-继承和动态内存分配" class="headerlink" title="13.6 继承和动态内存分配"></a>13.6 继承和动态内存分配</h3><h4 id="13-6-1-基类使用动态内存分配，派生类不使用动态内存分配"><a href="#13-6-1-基类使用动态内存分配，派生类不使用动态内存分配" class="headerlink" title="13.6.1 基类使用动态内存分配，派生类不使用动态内存分配"></a>13.6.1 基类使用动态内存分配，派生类不使用动态内存分配</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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">baseDMA</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="keyword">char</span> *label;</span><br><span class="line">    <span class="keyword">int</span> rating;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">baseDMA</span>(<span class="keyword">const</span> <span class="keyword">char</span> *l = <span class="string">&quot;null&quot;</span>, <span class="keyword">int</span> r = <span class="number">0</span>);</span><br><span class="line">    <span class="built_in">baseDMA</span>(<span class="keyword">const</span> baseDMA &amp;rs);</span><br><span class="line">    <span class="keyword">virtual</span> ~<span class="built_in">baseDMA</span>();</span><br><span class="line">    baseDMA &amp;<span class="keyword">operator</span>=(<span class="keyword">const</span> baseDMA &amp;rs);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>baseDMA类声明中包含了构造函数使用new时需要的特殊方法：析构函数、复制构造函数和重载赋值运算符。现在，从baseDMA派生出lackDMA类，而后者不使用new，也未包含其他一些不常用的、需要特殊处理的设计特性：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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="class"><span class="keyword">class</span> <span class="title">lacksDMA</span> :</span> <span class="keyword">public</span> baseDMA</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="keyword">char</span> color[<span class="number">40</span>];</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">...</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>是否需要为lackDMA类定义显示析构函数、复制构造函数和赋值运算符呢？<strong>不需要</strong>。</p><p>首先，来看是否需要析构函数。如果没有定义析构函数，编译器将定义一个不执行任何操作的默认构造函数。实际上，<strong>派生类的默认构造函数总是要进行一些操作：执行自身的代码后调用基类析构函数</strong>。因为lackDMA成员不需执行特殊操作，所以默认析构函数是合适的。</p><p>接下来看复制构造函数。复制类成员或继承的类组件时，则是使用该类的复制构造函数完成的。所以，lacksDMA类的默认复制构造函数使用显式baseDMA复制构造函数来复制lacksDMA对象的baseDMA部分。</p><p>对于赋值来说，类的默认赋值运算符将自动使用基类的赋值运算符来对基类组件进行赋值。</p><h4 id="13-6-2-基类使用动态内存分配，派生类也使用动态内存分配"><a href="#13-6-2-基类使用动态内存分配，派生类也使用动态内存分配" class="headerlink" title="13.6.2 基类使用动态内存分配，派生类也使用动态内存分配"></a>13.6.2 基类使用动态内存分配，派生类也使用动态内存分配</h4><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">hasDMA</span> :</span> <span class="keyword">public</span> baseDMA</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="keyword">char</span> *style;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">....</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>在这种情况下，必须为派生类定义显示析构函数、复制构造函数和赋值运算符。</p><p>首先看析构函数。派生类析构函数自动调用基类的析构函数，故其自身的职责是对派生类构造函数执行的动态内存分配进行清理。因此，hasDMA析构函数必须释放指针style管理的内存，并依赖于baseDMA的析构函数来释放指针label管理的内存。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">baseDMA::~<span class="built_in">baseDMA</span>()</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">delete</span> [] label;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">hasDMA::~<span class="built_in">hasDMA</span>()</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">delete</span> [] style;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>接下来看复制构造函数：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">baseDMA::<span class="built_in">baseDMA</span>(<span class="keyword">const</span> baseDMA &amp;rs)</span><br><span class="line">&#123;</span><br><span class="line">    label = <span class="keyword">new</span> <span class="keyword">char</span>[std::<span class="built_in">strlen</span>(rs.label) + <span class="number">1</span>];</span><br><span class="line">    std::<span class="built_in">strcpy</span>(label, rs.label);</span><br><span class="line">    rating = rs.rating;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">hasDMA::<span class="built_in">hasDMA</span>(<span class="keyword">const</span> hasDMA &amp;hs) : <span class="built_in">baseDMA</span>(hs)</span><br><span class="line">&#123;</span><br><span class="line">    style = <span class="keyword">new</span> <span class="keyword">char</span>[std::<span class="built_in">strlen</span>(hs.style) + <span class="number">1</span>];</span><br><span class="line">    std::<span class="built_in">strcpy</span>(style, hs.style);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>hasDMA复制构造函数只能访问hasDMA的数据，因此它必须调用baseDMA复制构造函数来处理共享的hasDMA数据。<strong>如果不这样做，它将自动调用基类的默认构造函数</strong>。</p><p>接下来看赋值运算符。baseDMA赋值运算符遵循下述常规模式：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">baseDMA &amp;baseDMA::<span class="keyword">operator</span>=(<span class="keyword">const</span> baseDMA &amp;rs)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">this</span> == &amp;rs)</span><br><span class="line">        <span class="keyword">return</span> *<span class="keyword">this</span>;</span><br><span class="line">    <span class="keyword">delete</span> [] label;</span><br><span class="line">    lable = <span class="keyword">new</span> <span class="keyword">char</span>[std::<span class="built_in">strlen</span>(rs.label) + <span class="number">1</span>];</span><br><span class="line">    std::<span class="built_in">strcpy</span>(label, rs.label);</span><br><span class="line">    rating = rs.rating;</span><br><span class="line">    <span class="keyword">return</span> *<span class="keyword">this</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>由于hasDMA也使用动态内存分配，所以它也需要一个<strong>显式赋值运算符</strong>。作为hasDMA的方法，它只能直接访问hasDMA的数据。然而，派生类的显示赋值运算符必须负责所有继承的baseDMA基类对象的赋值，可以通过显示调用基类赋值运算符来完成这项工作：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">hasDMA &amp;hasDMA::<span class="keyword">operator</span>=(<span class="keyword">const</span> hasDMA &amp;hs)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">this</span> == &amp;hs)</span><br><span class="line">        <span class="keyword">return</span> *<span class="keyword">this</span>;</span><br><span class="line">    baseDMA::<span class="keyword">operator</span>=(hs); <span class="comment">// 显式赋值运算符，不能写成*this = hs;这件导致调用hasDMA::operator=(hs)，进而递归调用</span></span><br><span class="line">    <span class="keyword">delete</span> [] style;</span><br><span class="line">    style = <span class="keyword">new</span> <span class="keyword">char</span>[std::<span class="built_in">strlen</span>(hs.style) + <span class="number">1</span>];</span><br><span class="line">    std::<span class="built_in">strcpy</span>(style, hs.style);</span><br><span class="line">    <span class="keyword">return</span> *<span class="keyword">this</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="第14章-C-中的代码重用"><a href="#第14章-C-中的代码重用" class="headerlink" title="第14章 C++中的代码重用"></a>第14章 C++中的代码重用</h2><h3 id="14-1-三种继承"><a href="#14-1-三种继承" class="headerlink" title="14.1 三种继承"></a>14.1 三种继承</h3><h4 id=""><a href="#" class="headerlink" title=""></a><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210420190706580.png" alt="image-20210420190706580"></h4><p>此处，私有继承能否隐式向上转换有待查证。</p><h4 id="14-1-1-使用using重新定义访问权限"><a href="#14-1-1-使用using重新定义访问权限" class="headerlink" title="14.1.1 使用using重新定义访问权限"></a>14.1.1 使用using重新定义访问权限</h4><p>使用保护派生或私有派生时，基类的公有成员将成为保护成员或私有成员。假设要让基类的方法在派生类外面可用，有两种方法：</p><ul><li><p>在派生类中定义一个使用该基类方法的派生类方法，相当于一个包装器，将基类方法包装在公有方法中。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">double</span> <span class="title">Student::sum</span><span class="params">()</span> <span class="keyword">const</span> <span class="comment">// public Student method</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> std::valarray&lt;<span class="keyword">double</span>&gt;::<span class="built_in">sum</span>(); <span class="comment">// use privately-inherited method</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>使用一个using声明来指出派生类可以使用特定的基类成员，即使采用私有派生。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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="class"><span class="keyword">class</span> <span class="title">Student</span> :</span> <span class="keyword">private</span> std::string, <span class="keyword">private</span> std::valarray&lt;<span class="keyword">double</span>&gt;</span><br><span class="line">&#123;</span><br><span class="line">...</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="keyword">using</span> std::valarray&lt;<span class="keyword">double</span>&gt;::min;</span><br><span class="line">    <span class="keyword">using</span> std::valarray&lt;<span class="keyword">double</span>&gt;::max;</span><br><span class="line">    ...</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>上述using声明使得<code>valarray&lt;double&gt;::min()</code>和<code>valarray&lt;double&gt;::max()</code>可用，就像它们是Student的公有方法一样。<strong>注意，using声明只使用成员名，没有圆括号、函数特征标和返回类型。</strong></p></li></ul><h3 id="14-2-多重继承"><a href="#14-2-多重继承" class="headerlink" title="14.2 多重继承"></a>14.2 多重继承</h3><h4 id="14-2-1-多个拷贝"><a href="#14-2-1-多个拷贝" class="headerlink" title="14.2.1 多个拷贝"></a>14.2.1 多个拷贝</h4><p>如果有以下继承层次：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Worker</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">  ...  </span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Waiter</span> :</span> <span class="keyword">public</span> Worker</span><br><span class="line">&#123;</span><br><span class="line">    ...</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Singer</span> :</span> <span class="keyword">public</span> Worker</span><br><span class="line">&#123;</span><br><span class="line">    ...</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SingingWaiter</span> :</span> <span class="keyword">public</span> Waiter, <span class="keyword">public</span> Singer</span><br><span class="line">&#123;</span><br><span class="line">    ...</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>上述是一个典型的菱形继承，SingingWaiter类的对象中将包含两个Worker对象的拷贝：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210428130102630.png" alt="image-20210428130102630"></p><p>使用指向Worker类的指针直接指向SingingWaiter的对象，将会出现二义性：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">SingingWaiter ed;</span><br><span class="line">Worker *pw = &amp;ed;  <span class="comment">// compile error</span></span><br></pre></td></tr></table></figure><p>需使用强制类型转换：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Worker *pw1 = (Waiter *)&amp;ed;</span><br><span class="line">Worker *pw2 = (Singer *)&amp;ed;</span><br></pre></td></tr></table></figure><h4 id="14-2-2-虚基类"><a href="#14-2-2-虚基类" class="headerlink" title="14.2.2 虚基类"></a>14.2.2 虚基类</h4><p>虚基类使得从多个类（它们的基类相同）派生出的对象只继承一个基类对象。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Worker</span> &#123;</span>...&#125;;</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Waiter</span> :</span> <span class="keyword">virtual</span> <span class="keyword">public</span> Worker &#123;...&#125;;</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Singer</span> :</span> <span class="keyword">public</span> <span class="keyword">virtual</span> Worker &#123;...&#125;;</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SingingWaiter</span> :</span> <span class="keyword">public</span> Waiter, <span class="keyword">public</span> Singer &#123;...&#125;;</span><br></pre></td></tr></table></figure><p>上述代码使得Worker类被用作Waiter和Singer的虚基类，virtual和public的次序无关紧要。而SingingWaiter对象将只包含Worker对象的一个副本。从本质上说，继承的Singer和Waiter对象共享一个Worker对象的一个副本：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/image-20210428140005245.png" alt="image-20210428140005245"></p><h5 id="1-新的构造函数规则"><a href="#1-新的构造函数规则" class="headerlink" title="1 - 新的构造函数规则"></a>1 - 新的构造函数规则</h5><p>对于非虚基类，唯一可以出现在初始化列表中的构造函数是即时基类构造函数（直接基类构造函数），而这些构造函数可能需要将信息传递给其基类。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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="class"><span class="keyword">class</span> <span class="title">A</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">    <span class="keyword">int</span> a;</span><br><span class="line">    </span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">A</span>(<span class="keyword">int</span> n = <span class="number">0</span>) : <span class="built_in">a</span>(n) &#123;&#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">B</span> :</span> <span class="keyword">public</span> A</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">int</span> b;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">B</span>(<span class="keyword">int</span> m = <span class="number">0</span>, <span class="keyword">int</span> n = <span class="number">0</span>) : <span class="built_in">A</span>(m), <span class="built_in">b</span>(n) &#123;&#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">C</span> :</span> <span class="keyword">public</span> B</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">int</span> c;</span><br><span class="line">   </span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">C</span>(<span class="keyword">int</span> m = <span class="number">0</span>, <span class="keyword">int</span> n = <span class="number">0</span>, <span class="keyword">int</span> q = <span class="number">0</span>) : <span class="built_in">B</span>(m, n), <span class="built_in">c</span>(q) &#123;&#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>C类的构造函数只能调用B类的构造函数，不能直接调用A类的构造函数，而B类的构造函数只能调用A类的构造函数。这里，C类的构造函数使用值q，并将m和n传递给B类的构造函数，而B类的构造函数使用n，并将m传递给A类的构造函数。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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="class"><span class="keyword">class</span> <span class="title">Worker</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    string fullname;</span><br><span class="line">   <span class="keyword">int</span> id;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">Worker</span>() : <span class="built_in">fullname</span>(<span class="string">&quot;&quot;</span>), <span class="built_in">id</span>(<span class="number">-1</span>) &#123;&#125;</span><br><span class="line">    <span class="built_in">Worker</span>(<span class="keyword">const</span> string &amp;s, <span class="keyword">int</span> n) : <span class="built_in">fullname</span>(s), <span class="built_in">id</span>(n) &#123;&#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Waiter</span> :</span> <span class="keyword">virtual</span> <span class="keyword">public</span> Worker</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="keyword">int</span> panache;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">Waiter</span>() : <span class="built_in">Worker</span>(), <span class="built_in">panache</span>(<span class="number">0</span>) &#123;&#125;</span><br><span class="line">    <span class="built_in">Waiter</span>(<span class="keyword">const</span> string &amp;s, <span class="keyword">int</span> n, <span class="keyword">int</span> p) : <span class="built_in">Worker</span>(s, n), <span class="built_in">panache</span>(p) &#123;&#125;</span><br><span class="line">    <span class="built_in">Waiter</span>(<span class="keyword">const</span> Worker &amp;wk, <span class="keyword">int</span> p) : <span class="built_in">Worker</span>(wk), <span class="built_in">panache</span>(p) &#123;&#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Singer</span> :</span> <span class="keyword">virtual</span> <span class="keyword">public</span> Worker</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="keyword">int</span> voice;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">Singer</span>() : <span class="built_in">Worker</span>(), <span class="built_in">voice</span>(<span class="number">0</span>) &#123;&#125;</span><br><span class="line">    <span class="built_in">Singer</span>(<span class="keyword">const</span> string &amp;s, <span class="keyword">int</span> n, <span class="keyword">int</span> v) : <span class="built_in">Worker</span>(s, n), <span class="built_in">voice</span>(v) &#123;&#125;</span><br><span class="line">    <span class="built_in">Singer</span>(<span class="keyword">const</span> Worker &amp;wk, <span class="keyword">int</span> v) : <span class="built_in">Worker</span>(wk), <span class="built_in">voice</span>(v) &#123;&#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SingingWaiter</span> :</span> <span class="keyword">public</span> Waiter, <span class="keyword">public</span> Singer</span><br><span class="line">&#123;</span><br><span class="line">  ...  </span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>如果Worker是虚基类，则这种信息自动传递将不起作用。例如，对于下面的MI构造函数：</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">SingingWaiter</span>(<span class="keyword">const</span> Worker &amp;wk, <span class="keyword">int</span> p, <span class="keyword">int</span> v) : <span class="built_in">Waiter</span>(wk, p), <span class="built_in">Singer</span>(wk, v) &#123;&#125;</span><br></pre></td></tr></table></figure><p>原因是，自动传递信息时，将通过两条不同的路径（Waiter和Singer）将wk传递给Worker对象。为避免这种冲突，C++在基类是虚的时，禁止信息通过中间类自动传递给基类。因此，上述构造函数将初始化panache和voice，但wk参数中的信息将不会传递给子对象Waiter。然而，编译器必须在构造派生对象之前构造基类对象组件，在上述情况下，编译器将使用Worker的默认构造函数。</p><p>如果不希望默认构造函数来构造虚基类对象，则需要显示地调用所需的基类构造函数：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">SingingWaiter</span>(<span class="keyword">const</span> Worker &amp;wk, <span class="keyword">int</span> p, <span class="keyword">int</span> v) : <span class="built_in">Worker</span>(wk), <span class="built_in">Waiter</span>(wk, p), <span class="built_in">Singer</span>(wk, v) &#123;&#125;</span><br></pre></td></tr></table></figure><p>上述代码对虚基类是合法的，但对非虚基类，则不合法。</p><blockquote><p>如果类有间接虚基类，则除非只需使用该虚基类的默认构造函数，否则必须显示地调用该虚基类地某个构造函数。</p></blockquote><h5 id="2-哪个方法"><a href="#2-哪个方法" class="headerlink" title="2 - 哪个方法"></a>2 - 哪个方法</h5><p>单根继承中，如果子类没有重新实现父类的方法，那么子类的将使用最近祖先中的定义。而在多重继承中，可能导致函数调用的二义性，例如，SingingWaiter的父类Waiter和Singer中都有Show方法，而SingingWaiter没有对Show方法重新实现，此时如果使用SingingWaiter的对象调用Show方法，将会导致二义性：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">SingingWaiter s;  <span class="comment">// 假设已经有了对应的构造函数的正确定义</span></span><br><span class="line">s.<span class="built_in">Show</span>();  <span class="comment">// compile error</span></span><br></pre></td></tr></table></figure><p>有两种方式解决以上问题，一是使用类作用域解析运算符：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">s.Singer::<span class="built_in">Show</span>();</span><br><span class="line">s.Waiter::<span class="built_in">Show</span>();</span><br></pre></td></tr></table></figure><p>另一种就是在SingingWaiter类中实现Show方法。</p><h5 id="3-虚基类和支配"><a href="#3-虚基类和支配" class="headerlink" title="3 - 虚基类和支配"></a>3 - 虚基类和支配</h5><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><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="class"><span class="keyword">class</span> <span class="title">B</span> &#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">short</span> <span class="title">q</span><span class="params">()</span></span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">C</span> :</span> <span class="keyword">virtual</span> <span class="keyword">public</span> B &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">long</span> <span class="title">q</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">int</span> <span class="title">omg</span><span class="params">()</span></span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">D</span> :</span> <span class="keyword">public</span> C &#123;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">E</span> :</span> <span class="keyword">virtual</span> <span class="keyword">public</span> B &#123;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="function"><span class="keyword">int</span> <span class="title">omg</span><span class="params">()</span></span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">F</span> :</span> <span class="keyword">public</span> D, <span class="keyword">public</span> E &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上述代码的继承树如下：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">   B</span><br><span class="line">  / \</span><br><span class="line"> /   \</span><br><span class="line">/     \</span><br><span class="line">C     E</span><br><span class="line">|     |</span><br><span class="line">D     |</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">     F</span><br></pre></td></tr></table></figure><p>C中的<code>q()</code>优先级比B中的<code>q()</code>优先级高，因此F中的方法可以用<code>q()</code>来表示<code>C::q()</code>。另一方面，C中的<code>omg()</code>与E中的<code>omg()</code>谁也不比谁的优先级高，因此F中使用非限定的<code>omg()</code>将导致二义性。</p><p>虚二义性规则与访问规则无关，从上面的例子可以看到，C中的<code>omg()</code>访问权限为public，而E中的<code>omg()</code>访问权限为private，不能在F中访问<code>E::omg()</code>，但使用<code>omg()</code>仍将导致二义性。</p><h3 id="14-3-类模板"><a href="#14-3-类模板" class="headerlink" title="14.3 类模板"></a>14.3 类模板</h3><h4 id="14-3-1-类模板定义与使用"><a href="#14-3-1-类模板定义与使用" class="headerlink" title="14.3.1 类模板定义与使用"></a>14.3.1 类模板定义与使用</h4><h4 id="14-3-2-非类型参数"><a href="#14-3-2-非类型参数" class="headerlink" title="14.3.2 非类型参数"></a>14.3.2 非类型参数</h4><h4 id="14-3-3-模板的多功能性"><a href="#14-3-3-模板的多功能性" class="headerlink" title="14.3.3 模板的多功能性"></a>14.3.3 模板的多功能性</h4><ul><li>递归使用模板</li><li>使用多个类型参数</li></ul><h4 id="14-3-4-模板的具体化"><a href="#14-3-4-模板的具体化" class="headerlink" title="14.3.4 模板的具体化"></a>14.3.4 模板的具体化</h4><ul><li>隐式实例化</li><li>显示实例化</li><li><p>显示具体化</p></li><li><p>部分具体化</p></li></ul><h4 id="14-3-5-将模板用作参数"><a href="#14-3-5-将模板用作参数" class="headerlink" title="14.3.5 将模板用作参数"></a>14.3.5 将模板用作参数</h4><h4 id="14-3-6-模板类和友元"><a href="#14-3-6-模板类和友元" class="headerlink" title="14.3.6 模板类和友元"></a>14.3.6 模板类和友元</h4><ul><li>非模板友元</li><li>约束模板友元</li><li>非约束模板友元</li></ul><h3 id="14-4-C-定义别名"><a href="#14-4-C-定义别名" class="headerlink" title="14.4 C++定义别名"></a>14.4 C++定义别名</h3><ul><li>typedef</li><li>using</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;C-Primer-Plus&quot;&gt;&lt;a href=&quot;#C-Primer-Plus&quot; class=&quot;headerlink&quot; title=&quot;C++ Primer Plus&quot;&gt;&lt;/a&gt;C++ Primer Plus&lt;/h1&gt;&lt;h2 id=&quot;第1章-预备知识&quot;&gt;&lt;a href</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>十大经典排序算法</title>
    <link href="http://example.com/2021/04/20/sorted-algorithm/"/>
    <id>http://example.com/2021/04/20/sorted-algorithm/</id>
    <published>2021-04-20T15:32:20.000Z</published>
    <updated>2021-04-22T16:22:49.699Z</updated>
    
    <content type="html"><![CDATA[<h3 id="1-冒泡排序"><a href="#1-冒泡排序" class="headerlink" title="1. 冒泡排序"></a>1. 冒泡排序</h3><h4 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h4><p>冒泡排序是一种比较排序，且会交换元素位置。以从小到大排序为例，每轮排序过程会比较相邻两数大小，如果大数在前，则交换相邻两束位置，因此每轮将得到剩余元素中的最大值。</p><h4 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</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><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="function"><span class="keyword">void</span> <span class="title">bubble_sort</span><span class="params">(<span class="keyword">int</span> arr[], <span class="keyword">int</span> len)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">bool</span> did_swap;</span><br><span class="line">    <span class="keyword">int</span> temp;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; len - <span class="number">1</span>; ++i)</span><br><span class="line">    &#123;</span><br><span class="line">        did_swap = <span class="literal">false</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j &lt; len - i - <span class="number">1</span>; ++j)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">if</span> (arr[j] &gt; arr[j + <span class="number">1</span>])</span><br><span class="line">            &#123;</span><br><span class="line">                temp = arr[j];</span><br><span class="line">                arr[j] = arr[j + <span class="number">1</span>];</span><br><span class="line">                arr[j + <span class="number">1</span>] = temp;</span><br><span class="line">                did_swap = <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (did_swap == <span class="literal">false</span>)</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="复杂度分析"><a href="#复杂度分析" class="headerlink" title="复杂度分析"></a>复杂度分析</h4><p>最好情况下，数组已经排好序，仍需进行1轮的比较，时间复杂度为$O\left(n\right)$。最坏情况下，数组为倒序，总共需要$n^2$次比较，因此最坏时间复杂度为$O\left(n^2\right)$；平均时间复杂度为$O\left(n^2\right)$。</p><p>由于使用原数组进行元素交换排序，借用一个临时变量，因此时间复杂度为$O\left(1\right)$。</p><h4 id="稳定性"><a href="#稳定性" class="headerlink" title="稳定性"></a>稳定性</h4><p>使用交换元素进行排序，如果数相同，不会进行交换位置，因此冒泡排序为稳定排序。</p><h3 id="2-选择排序"><a href="#2-选择排序" class="headerlink" title="2.选择排序"></a>2.选择排序</h3><h4 id="思路-1"><a href="#思路-1" class="headerlink" title="思路"></a>思路</h4><p>选择排序是一种简单直观的排序算法，它的工作原理是首先在未排序序列中找到最小（大）元素，存放到排序序列的起始位置，然后，再从剩余未排序元素中继续寻找最小（大）元素，然后放到已排序序列的末尾。以此类推，直到所有元素均排序完毕。</p><h4 id="代码实现-1"><a href="#代码实现-1" class="headerlink" title="代码实现"></a>代码实现</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><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="function"><span class="keyword">void</span> <span class="title">selection_sort</span><span class="params">(<span class="keyword">int</span> arr[], <span class="keyword">int</span> len)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> min_index, temp;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; len - <span class="number">1</span>; ++i)</span><br><span class="line">    &#123;</span><br><span class="line">        min_index = i;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> j = i + <span class="number">1</span>; j &lt; len; ++j)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">if</span> (arr[min_index] &gt; arr[j])</span><br><span class="line">                min_index = j;</span><br><span class="line">        &#125;</span><br><span class="line">        temp = arr[i];</span><br><span class="line">        arr[i] = arr[min_index];</span><br><span class="line">        arr[min_index] = temp;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="复杂度分析-1"><a href="#复杂度分析-1" class="headerlink" title="复杂度分析"></a>复杂度分析</h4><p>无论什么数据进去，都会进行进行$\frac{n\left(n-1\right)}{2}$次比较，因此时间复杂度无论最好最坏还是平均，都是$O\left(n^2\right)$。</p><p>由于只是借用临时变量来存储最小值索引和交换时所用的临时变量，因此空间复杂度为$O\left(1\right)$。</p><h4 id="稳定性-1"><a href="#稳定性-1" class="headerlink" title="稳定性"></a>稳定性</h4><p>选择排序是不稳定排序，例如5、8、5、2、9这个序列，第一次将第一个5和2交换，第一个5到了第二个5的后面。</p><h3 id="3-插入排序"><a href="#3-插入排序" class="headerlink" title="3.插入排序"></a>3.插入排序</h3><h4 id="思路-2"><a href="#思路-2" class="headerlink" title="思路"></a>思路</h4><p>插入排序的基本思想是将一个记录插入到已经排好序的有序表中，从而一个新的、记录数增1的有序表。插入排序需要在有序表中查找合适的位置，通常从后往前扫描，找到相应的位置并插入。</p><h4 id="代码实现-2"><a href="#代码实现-2" class="headerlink" title="代码实现"></a>代码实现</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><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="function"><span class="keyword">void</span> <span class="title">insert_sort</span><span class="params">(<span class="keyword">int</span> arr[], <span class="keyword">int</span> len)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> temp;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">1</span>; i &lt; len; ++i)</span><br><span class="line">    &#123;</span><br><span class="line">        temp = arr[i]; <span class="comment">// 注意下标</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> j = i; j &gt; <span class="number">0</span> &amp;&amp; arr[j - <span class="number">1</span>] &gt; temp; --j)</span><br><span class="line">        &#123;</span><br><span class="line">            arr[j] = arr[j - <span class="number">1</span>];</span><br><span class="line">        &#125;</span><br><span class="line">        arr[j] = temp;</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="keyword">void</span> <span class="title">insert_sort</span><span class="params">(<span class="keyword">int</span> arr[], <span class="keyword">int</span> len)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> temp, pre;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">1</span>; i &lt; len; ++i)</span><br><span class="line">    &#123;</span><br><span class="line">        temp = arr[i];</span><br><span class="line">        pre = i - <span class="number">1</span>; <span class="comment">// 注意下标</span></span><br><span class="line">        <span class="keyword">while</span> (pre &gt;= <span class="number">0</span> &amp;&amp; arr[pre] &gt; temp)</span><br><span class="line">        &#123;</span><br><span class="line">            arr[pre + <span class="number">1</span>] = arr[pre];</span><br><span class="line">            --pre;</span><br><span class="line">        &#125;</span><br><span class="line">        arr[pre + <span class="number">1</span>] = temp;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="复杂度分析-2"><a href="#复杂度分析-2" class="headerlink" title="复杂度分析"></a>复杂度分析</h4><p>最坏情况下数组逆序，需要比较$\frac{n\left(n - 1\right)}{2}$次，时间复杂度为$O\left(n^2\right)$。最好情况下，数组有序，只需要跟前一个元素比较一次，共$n-1$次，时间复杂度为$O\left(n\right)$。平均时间复杂度为$O\left(n^2\right)$。</p><p>空间复杂度为$O\left(1\right)$。</p><h4 id="稳定性-2"><a href="#稳定性-2" class="headerlink" title="稳定性"></a>稳定性</h4><p>由于比较用的是<code>arr[pre] &gt; temp</code>，因此不会向左查询位置时，不会越过与自身相同的值，为稳定排序。</p><h3 id="4-希尔排序"><a href="#4-希尔排序" class="headerlink" title="4. 希尔排序"></a>4. 希尔排序</h3><h4 id="思路-3"><a href="#思路-3" class="headerlink" title="思路"></a>思路</h4><p>希尔排序可以说是对插入排序的一种改进，通过设置增量将序列分组进行插入排序。随着增量逐渐减少，每组包含的数越来越多，当增量减为1时，整个序列恰好被分成一组，算法便终止。增量为几，序列就被分成几组。下面来看一个具体的排序案例：</p><p>假设序列长度为<code>len</code>，我们设置增量<code>gap</code>的初值为<code>len / 2</code>，并以<code>gap /= 2</code>的方式减少增量形成一个增量序列：$\left(\frac{1}{2}len，\frac{1}{4}len，…，1\right)$。这里的除法为整除，舍弃余数部分。希尔排序的增量序列的选择与证明是个数学难题，我们选择的这个增量序列是比较常用的，也是希尔建议的增量，称为希尔增量，但其实这个增量序列不是最优的。下面是整个过程：</p><p><img src="https://images2015.cnblogs.com/blog/1024555/201611/1024555-20161128110416068-1421707828.png" style="zoom: 50%;" /></p><h4 id="代码实现-3"><a href="#代码实现-3" class="headerlink" title="代码实现"></a>代码实现</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><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="function"><span class="keyword">void</span> <span class="title">shell_sort</span><span class="params">(<span class="keyword">int</span> arr[], <span class="keyword">int</span> len)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> gap = len / <span class="number">2</span>; gap &gt; <span class="number">0</span>; gap /= <span class="number">2</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">/* 分组插入排序 */</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = gap; i &lt; len; ++i)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">int</span> temp = arr[i];</span><br><span class="line">            <span class="keyword">int</span> j = i;</span><br><span class="line">            <span class="keyword">while</span> (j - gap &gt;= <span class="number">0</span> &amp;&amp; arr[j - gap] &gt; arr[j])</span><br><span class="line">            &#123;</span><br><span class="line">                arr[j] = arr[j - gap];</span><br><span class="line">                j -= gap;</span><br><span class="line">            &#125;</span><br><span class="line">            arr[j] = temp;</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>上述代码实现时，内部for循环i的初值设置为gap，是因为下标为gap的元素即第一组的第二个元素，插入排序一般从第二个元素开始。</p><h4 id="复杂度分析-3"><a href="#复杂度分析-3" class="headerlink" title="复杂度分析"></a>复杂度分析</h4><p>希尔排序最好情况下时间复杂度为$O\left(n\right)$，最坏情况下时间复杂度为$O\left(n^2\right)$。平均时间复杂度为$O\left(n^{1.3}\right)$。这里说的应该不一定指的是使用上述的增量序列的时间复杂度。</p><p>由于只是借助几个临时变量，且是在原数组上进行操作的，并未有额外的内存开销，因此空间复杂度为$O\left(1\right)$。</p><h4 id="稳定性-3"><a href="#稳定性-3" class="headerlink" title="稳定性"></a>稳定性</h4><p>由于通过增量进行分组并分别在组内排序，希尔排序为不稳定排序。</p><h3 id="5-归并排序"><a href="#5-归并排序" class="headerlink" title="5. 归并排序"></a>5. 归并排序</h3><h4 id="思路-4"><a href="#思路-4" class="headerlink" title="思路"></a>思路</h4><p>归并排序采用分治法，将序列分成两份，对两个子序列分别排完序后进行归并。而子序列的排序采用同样的策略，直到子序列的长度为1。</p><h4 id="代码实现-4"><a href="#代码实现-4" class="headerlink" title="代码实现"></a>代码实现</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><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="function"><span class="keyword">void</span> <span class="title">merge</span><span class="params">(<span class="keyword">int</span> arr[], <span class="keyword">int</span> bak[], <span class="keyword">int</span> start, <span class="keyword">int</span> end)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (start &gt;= end)</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">int</span> mid = (end - start) &gt;&gt; <span class="number">1</span> + start;</span><br><span class="line">    <span class="keyword">int</span> start1 = start, end1 = mid;</span><br><span class="line">    <span class="keyword">int</span> start2 = mid + <span class="number">1</span>, end2 = end;</span><br><span class="line">    </span><br><span class="line">    <span class="built_in">merge</span>(arr, bak, start, mid);</span><br><span class="line">    <span class="built_in">merge</span>(arr, bak, mid + <span class="number">1</span>, end);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/* 归并 */</span></span><br><span class="line">    <span class="keyword">int</span> k = start;</span><br><span class="line">    <span class="keyword">while</span> (start1 &lt;= end1 &amp;&amp; start &lt;= end2)</span><br><span class="line">        bak[k++] = arr[start1] &lt; arr[start2] ? arr[start1++] : arr[start2++];</span><br><span class="line">    <span class="keyword">while</span> (start1 &lt;= end1)</span><br><span class="line">        bak[k++] = arr[start1++];</span><br><span class="line">    <span class="keyword">while</span> (start2 &lt;= end2)</span><br><span class="line">        bak[k++] = arr[start2++];</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = start; i &lt;= end; ++i)</span><br><span class="line">        arr[i] = bak[i];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">merge_sort</span><span class="params">(<span class="keyword">int</span> arr[], <span class="keyword">int</span> len)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> *backup = <span class="keyword">new</span> <span class="keyword">int</span>[len];</span><br><span class="line">    <span class="built_in">merge</span>(arr, backup, <span class="number">0</span>, len - <span class="number">1</span>);</span><br><span class="line">    <span class="keyword">delete</span>[] backup;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="复杂度分析-4"><a href="#复杂度分析-4" class="headerlink" title="复杂度分析"></a>复杂度分析</h4><p>归并排序的时间复杂度无论是最好还是最坏情况下都是$O\left(nlogn\right)$。</p><p>由于需要借助额外的空间来存储中间过程，空间复杂度为$O\left(n\right)$。</p><h4 id="稳定性-4"><a href="#稳定性-4" class="headerlink" title="稳定性"></a>稳定性</h4><p>归并排序是稳定的。</p><p>未完待续….</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;1-冒泡排序&quot;&gt;&lt;a href=&quot;#1-冒泡排序&quot; class=&quot;headerlink&quot; title=&quot;1. 冒泡排序&quot;&gt;&lt;/a&gt;1. 冒泡排序&lt;/h3&gt;&lt;h4 id=&quot;思路&quot;&gt;&lt;a href=&quot;#思路&quot; class=&quot;headerlink&quot; title=&quot;思路&quot;</summary>
      
    
    
    
    <category term="算法" scheme="http://example.com/categories/%E7%AE%97%E6%B3%95/"/>
    
    
    <category term="排序" scheme="http://example.com/tags/%E6%8E%92%E5%BA%8F/"/>
    
  </entry>
  
  <entry>
    <title>剑指Offer09 - 用两个栈实现队列</title>
    <link href="http://example.com/2021/04/16/algorithm1/"/>
    <id>http://example.com/2021/04/16/algorithm1/</id>
    <published>2021-04-16T14:14:27.000Z</published>
    <updated>2021-04-16T14:35:28.220Z</updated>
    
    <content type="html"><![CDATA[<h3 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h3><blockquote><p>用两个栈实现一个队列。队列的生命如下，请实现它的两个函数<code>appendTail</code>和<code>deleteHead</code>，分别完成在队列尾部插入整数和在队列头部删除整数的功能。（若队列中没有元素，<code>deleteHead</code>操作返回-1）。</p></blockquote><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Your CQueue object will be instantiated and called as such:</span></span><br><span class="line"><span class="comment"> * CQueue* obj = new CQueue();</span></span><br><span class="line"><span class="comment"> * obj-&gt;appendTail(value);</span></span><br><span class="line"><span class="comment"> * int param_2 = obj-&gt;deleteHead();</span></span><br><span class="line"><span class="comment"> */</span></span><br></pre></td></tr></table></figure><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/offer09.png" style="zoom: 50%; clear: both; margin: auto; display: block;"/></p><h3 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h3><p>使用两个栈实现队列，考察的是栈和队列的工作方式。栈是后进先出，队列是先进先出。假设有栈s1和栈s2，分析如下：</p><ul><li>入队列：直接入栈s1</li><li>出队列：<ul><li>如果栈s2为空，栈s1不为空，由于出栈是从栈顶弹出元素，而出队列是从队列首弹出元素，因此不能直接从栈s1出栈，需要借助栈s2。可以将s1中的元素逐个弹出并压入s2中，此时，最后入栈s1的就在栈s1的栈底，而最先入栈s1的元素在栈s2的栈顶。因此只要再从s2出栈，即可拿到最先入队列的元素。</li><li>如果栈s2为空，栈s1也为空，则队列中没有任何元素，返回-1。</li><li>如果栈s2不为空，可直接从s2出栈，得到的就是当前队列的队首元素。</li></ul></li></ul><h3 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><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="class"><span class="keyword">class</span> <span class="title">CQueue</span> &#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    stack&lt;<span class="keyword">int</span>&gt; s1, s2;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">CQueue</span>() &#123;</span><br><span class="line">        <span class="keyword">while</span> (!s1.<span class="built_in">empty</span>())</span><br><span class="line">            s1.<span class="built_in">pop</span>();</span><br><span class="line">        <span class="keyword">while</span> (!s2.<span class="built_in">empty</span>())</span><br><span class="line">            s2.<span class="built_in">pop</span>();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">appendTail</span><span class="params">(<span class="keyword">int</span> value)</span> </span>&#123;</span><br><span class="line">        s1.<span class="built_in">push</span>(value);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">int</span> <span class="title">deleteHead</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">int</span> temp;</span><br><span class="line">        <span class="keyword">if</span> (s2.<span class="built_in">empty</span>())</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">while</span> (!s1.<span class="built_in">empty</span>())</span><br><span class="line">            &#123;</span><br><span class="line">                temp = s1.<span class="built_in">top</span>();</span><br><span class="line">                s1.<span class="built_in">pop</span>();</span><br><span class="line">                s2.<span class="built_in">push</span>(temp);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (s2.<span class="built_in">empty</span>())</span><br><span class="line">            <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">        temp = s2.<span class="built_in">top</span>();</span><br><span class="line">        s2.<span class="built_in">pop</span>();</span><br><span class="line">        <span class="keyword">return</span> temp;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;题目描述&quot;&gt;&lt;a href=&quot;#题目描述&quot; class=&quot;headerlink&quot; title=&quot;题目描述&quot;&gt;&lt;/a&gt;题目描述&lt;/h3&gt;&lt;blockquote&gt;
&lt;p&gt;用两个栈实现一个队列。队列的生命如下，请实现它的两个函数&lt;code&gt;appendTail&lt;/cod</summary>
      
    
    
    
    <category term="算法题" scheme="http://example.com/categories/%E7%AE%97%E6%B3%95%E9%A2%98/"/>
    
    
    <category term="栈" scheme="http://example.com/tags/%E6%A0%88/"/>
    
    <category term="队列" scheme="http://example.com/tags/%E9%98%9F%E5%88%97/"/>
    
    <category term="C++" scheme="http://example.com/tags/C/"/>
    
  </entry>
  
  <entry>
    <title>Hello World - Github+Hexo搭建个人博客</title>
    <link href="http://example.com/2021/04/16/hello-world/"/>
    <id>http://example.com/2021/04/16/hello-world/</id>
    <published>2021-04-16T14:11:12.697Z</published>
    <updated>2021-04-16T14:11:12.697Z</updated>
    
    <content type="html"><![CDATA[<p>搭建个人博客有很多平台，比较受欢迎的就是基于Github使用Hexo框架搭建个人博客了。本文记载了一次使用Github+Hexo搭建个人博客的过程。</p><h2 id="1-准备工作"><a href="#1-准备工作" class="headerlink" title="1 准备工作"></a>1 准备工作</h2><h3 id="1-1-为Github配置ssh"><a href="#1-1-为Github配置ssh" class="headerlink" title="1.1 为Github配置ssh"></a>1.1 为Github配置ssh</h3><h4 id="1-1-1-生成新的ssh密钥"><a href="#1-1-1-生成新的ssh密钥" class="headerlink" title="1.1.1 生成新的ssh密钥"></a>1.1.1 生成新的ssh密钥</h4><ol><li><p>打开终端，Windows上最好使用Git Bash，配置用户名和邮箱：</p><figure class="highlight shell"><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="meta">$</span><span class="bash"> git config --global user.name <span class="string">&quot;your_username&quot;</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> git config --global user.email <span class="string">&quot;your_email@example.com&quot;</span></span></span><br></pre></td></tr></table></figure></li><li><p>生成新的ssh密钥</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">$ ssh-keygen -t rsa -b <span class="number">4096</span> -C <span class="string">&quot;your_email@example.com&quot;</span></span><br></pre></td></tr></table></figure></li><li><p>当提示输入一个文件来存储密钥时，按回车选择默认文件就好</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;</span><span class="bash"> Enter a file <span class="keyword">in</span> <span class="built_in">which</span> to save the key (~/.ssh/id_rsa): [Press Enter]</span></span><br></pre></td></tr></table></figure></li><li><p>当提示输入私钥密码时，按回车表示不使用私钥密码，也可以自行设置</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&gt; <span class="function">Enter <span class="title">passphrase</span> <span class="params">(empty <span class="keyword">for</span> no passphrase)</span>: [Type a passphrase or press enter]</span></span><br><span class="line"><span class="function">&gt; Enter same passphrase again: [Type a passphrase or press enter]</span></span><br></pre></td></tr></table></figure></li></ol><p>生成好后，密钥保存在<code>~/.ssh/id_rsa</code>文件中。</p><h4 id="1-1-2-添加ssh密钥到ssh代理"><a href="#1-1-2-添加ssh密钥到ssh代理" class="headerlink" title="1.1.2 添加ssh密钥到ssh代理"></a>1.1.2 添加ssh密钥到ssh代理</h4><ol><li><p>后台启动ssh-agent</p><figure class="highlight shell"><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="meta">$</span><span class="bash"> <span class="built_in">eval</span> <span class="string">&quot;<span class="subst">$(ssh-agent -s)</span>&quot;</span></span></span><br><span class="line"><span class="meta">&gt;</span><span class="bash"> Agent pid 59566</span></span><br></pre></td></tr></table></figure></li><li><p>根据官网的配置教程，如果你的系统是macOS Sierra 10.12.2或更高版本，需要修改<code>~/.ssh/config</code>文件自动加载密钥到ssh-agent，并在密钥链存储私钥密码。</p><ul><li><p>检查<code>~/.ssh/config</code>文件是否存在</p><figure class="highlight shell"><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="meta">$</span><span class="bash"> open ~/.ssh/config</span></span><br><span class="line"><span class="meta">&gt;</span><span class="bash"> The file /Users/you/.ssh/config does not exist.</span></span><br></pre></td></tr></table></figure></li><li><p>如果文件不存在，则创建</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> touch ~/.ssh/config</span></span><br></pre></td></tr></table></figure></li><li><p>编辑<code>~/.ssh/config</code>文件</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><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">Host *</span><br><span class="line">Add KeysToAgent yes</span><br><span class="line">UseKeychain yes</span><br><span class="line">IdentityFile ~/.ssh/id_rsa</span><br></pre></td></tr></table></figure></li></ul></li><li><p>添加你的ssh私钥到ssh-agent并且存储私钥密码</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> ssh-add -K ~/.ssh/id_rsa</span></span><br></pre></td></tr></table></figure></li></ol><h4 id="1-1-3-添加ssh公钥到Github账号"><a href="#1-1-3-添加ssh公钥到Github账号" class="headerlink" title="1.1.3 添加ssh公钥到Github账号"></a>1.1.3 添加ssh公钥到Github账号</h4><ol><li><p>复制ssh公钥，可以手动复制，也可以使用命令复制</p><figure class="highlight shell"><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="meta">$</span><span class="bash"> clip &lt; ~/.ssh/id_rsa.pub</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> Copies the contents of the id_rsa.pub file to your clipboard</span></span><br></pre></td></tr></table></figure></li><li><p>打开Github，选择用户下的设置</p><p><img style="clear: both; margin: auto; display: block; zoom: 50%;" src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/github_setting.png"/></p></li><li><p>选择”SSH and GPG keys”</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/ssh_and_gpg_keys.png" style="clear: both; margin: auto; display: block; zoom:50%;" /></p></li><li><p>点击”New SSH key”</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/new_ssh_key.png" style="zoom:50%; clear: both; display: block; margin: auto;" /></p></li><li><p>将刚才复制的ssh公钥粘贴到key一栏，点击“Add SSH key”添加公钥</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/add_ssh_key.png" style="zoom: 40%; clear: both; display: block; margin: auto;" /></p></li></ol><h4 id="1-1-4-测试配置是否成功"><a href="#1-1-4-测试配置是否成功" class="headerlink" title="1.1.4 测试配置是否成功"></a>1.1.4 测试配置是否成功</h4><ol><li><p>打开终端或Git Bash，输入命令</p><figure class="highlight shell"><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="meta">$</span><span class="bash"> ssh -T git@github.com</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> Attempts to ssh to Github</span></span><br></pre></td></tr></table></figure><p>你可能会看到如下警告：</p><figure class="highlight shell"><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="meta">&gt;</span><span class="bash"> The authenticity of host <span class="string">&#x27;github.com (IP ADDRESS)&#x27;</span> can<span class="string">&#x27;t be established.</span></span></span><br><span class="line"><span class="meta">&gt;</span><span class="bash"> RSA key fingerprint is SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8.</span></span><br><span class="line"><span class="meta">&gt;</span><span class="bash"> Are you sure you want to <span class="built_in">continue</span> connecting (yes/no)?</span></span><br></pre></td></tr></table></figure></li><li><p>输入”yes”，如果显示以下信息，说明配置成功</p><figure class="highlight shell"><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="meta">&gt;</span><span class="bash"> Hi username! You<span class="string">&#x27;ve successfully authenticated, but GitHub does not</span></span></span><br><span class="line"><span class="meta">&gt;</span><span class="bash"> provide shell access.</span></span><br></pre></td></tr></table></figure></li><li><p>如果你接收到了一个“permission denied”消息，说明配置失败，请参考<a class="link"   href="https://docs.github.com/en/github/authenticating-to-github/error-permission-denied-publickey" >Error: Permission denied (publickey)<i class="fas fa-external-link-alt"></i></a>。</p></li></ol><h3 id="1-2-安装Node-js"><a href="#1-2-安装Node-js" class="headerlink" title="1.2 安装Node.js"></a>1.2 安装Node.js</h3><h4 id="1-2-1-下载安装Node-js"><a href="#1-2-1-下载安装Node-js" class="headerlink" title="1.2.1 下载安装Node.js"></a>1.2.1 下载安装Node.js</h4><p>MacOS下安装Node.js只需在<a class="link"   href="https://nodejs.org/en/" >官网<i class="fas fa-external-link-alt"></i></a>下载<code>.dmg</code>包安装即可。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/nodejs.png" style="zoom: 40%; clear: both; display: block; margin: auto;"/></p><h4 id="1-2-2-修改镜像为淘宝镜像"><a href="#1-2-2-修改镜像为淘宝镜像" class="headerlink" title="1.2.2 修改镜像为淘宝镜像"></a>1.2.2 修改镜像为淘宝镜像</h4><p>国内推荐使用淘宝镜像加速npm下载，可使用如下命令配置：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> npm install -g cnpm --registry=https://registry.npm.taobao.org</span></span><br></pre></td></tr></table></figure><h2 id="2-博客搭建"><a href="#2-博客搭建" class="headerlink" title="2 博客搭建"></a>2 博客搭建</h2><h3 id="2-1-创建Github仓库"><a href="#2-1-创建Github仓库" class="headerlink" title="2.1 创建Github仓库"></a>2.1 创建Github仓库</h3><p>以Github为平台，需要首先新建一个Github仓库，仓库名为<code>username.github.io</code>。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/new_repo.png" style="zoom: 50%; clear: both; display: block; margin: auto;"/></p><h3 id="2-2-初始化本地环境"><a href="#2-2-初始化本地环境" class="headerlink" title="2.2 初始化本地环境"></a>2.2 初始化本地环境</h3><ol><li><p>安装hexo框架：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> npm install -g hexo</span></span><br></pre></td></tr></table></figure></li><li><p>新建本地目录，并使用hexo初始化：</p><figure class="highlight shell"><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="meta">$</span><span class="bash"> mkdir hexo &amp;&amp; <span class="built_in">cd</span> hexo</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> hexo init</span></span><br></pre></td></tr></table></figure></li><li><p>在本地预览博客效果：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> hexo server</span></span><br></pre></td></tr></table></figure></li></ol><h3 id="2-3-推送到Github仓库"><a href="#2-3-推送到Github仓库" class="headerlink" title="2.3 推送到Github仓库"></a>2.3 推送到Github仓库</h3><ol><li><p>配置博客根目录下的<code>_config.yml</code>:</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">language:</span> <span class="string">zh</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Deployment</span></span><br><span class="line"><span class="comment">## Docs: https://hexo.io/docs/one-command-deployment</span></span><br><span class="line"><span class="attr">deploy:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">git</span></span><br><span class="line">  <span class="attr">repo:</span> <span class="string">git@github.com:username/username.github.io.git</span></span><br><span class="line">  <span class="attr">branch:</span> <span class="string">master</span></span><br></pre></td></tr></table></figure><p>注意，<code>username</code>字段请自行替换成自己的github用户名。</p></li><li><p>推送到github仓库</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> hexo deploy</span></span><br></pre></td></tr></table></figure></li><li><p>访问<code>https://username.github.io</code>，即可查看博客主页。</p></li></ol><h2 id="Hexo常用命令"><a href="#Hexo常用命令" class="headerlink" title="Hexo常用命令"></a>Hexo常用命令</h2><h4 id="3-1-Create-a-new-post"><a href="#3-1-Create-a-new-post" class="headerlink" title="3.1 Create a new post"></a>3.1 Create a new post</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">$ hexo new <span class="string">&quot;My New Post&quot;</span></span><br></pre></td></tr></table></figure><p>More info: <a class="link"   href="https://hexo.io/docs/writing.html" >Writing<i class="fas fa-external-link-alt"></i></a></p><h4 id="Run-server"><a href="#Run-server" class="headerlink" title="Run server"></a>Run server</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">$ hexo server</span><br></pre></td></tr></table></figure><p>More info: <a class="link"   href="https://hexo.io/docs/server.html" >Server<i class="fas fa-external-link-alt"></i></a></p><h4 id="3-3-Generate-static-files"><a href="#3-3-Generate-static-files" class="headerlink" title="3.3 Generate static files"></a>3.3 Generate static files</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">$ hexo generate</span><br></pre></td></tr></table></figure><p>More info: <a class="link"   href="https://hexo.io/docs/generating.html" >Generating<i class="fas fa-external-link-alt"></i></a></p><h4 id="3-4-Deploy-to-remote-sites"><a href="#3-4-Deploy-to-remote-sites" class="headerlink" title="3.4 Deploy to remote sites"></a>3.4 Deploy to remote sites</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">$ hexo deploy</span><br></pre></td></tr></table></figure><p>More info: <a class="link"   href="https://hexo.io/docs/one-command-deployment.html" >Deployment<i class="fas fa-external-link-alt"></i></a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;搭建个人博客有很多平台，比较受欢迎的就是基于Github使用Hexo框架搭建个人博客了。本文记载了一次使用Github+Hexo搭建个人博客的过程。&lt;/p&gt;
&lt;h2 id=&quot;1-准备工作&quot;&gt;&lt;a href=&quot;#1-准备工作&quot; class=&quot;headerlink&quot; title</summary>
      
    
    
    
    <category term="环境搭建" scheme="http://example.com/categories/%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/"/>
    
    
    <category term="github" scheme="http://example.com/tags/github/"/>
    
    <category term="hexo" scheme="http://example.com/tags/hexo/"/>
    
  </entry>
  
  <entry>
    <title>Windows 10操作系统为Visual Studio 2019配置PCL 1.11.0</title>
    <link href="http://example.com/2021/04/15/VS-PCL-Config-Win/"/>
    <id>http://example.com/2021/04/15/VS-PCL-Config-Win/</id>
    <published>2021-04-15T12:44:04.000Z</published>
    <updated>2021-04-16T14:10:43.037Z</updated>
    
    <content type="html"><![CDATA[<h3 id="下载安装PCL"><a href="#下载安装PCL" class="headerlink" title="下载安装PCL"></a>下载安装PCL</h3><p>访问PCL的<a class="link"   href="https://github.com/PointCloudLibrary/pcl" >Github官网<i class="fas fa-external-link-alt"></i></a>，进入<a class="link"   href="https://github.com/PointCloudLibrary/pcl/releases" >Release页面<i class="fas fa-external-link-alt"></i></a>，找到<em>PCL 1.11.0</em>，点击进入后在Assets下面可看到4个文件，前两个就是我们需要的。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/pcl8.png" alt=""></p><p>首先安装<code>.exe</code>包，选择添加环境变量时，勾选为所有用户添加环境变量：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/pcl3.png" alt=""></p><p>安装路径可以自行更改，这里以<code>D:\pcl-1.11.0</code>为例，路径中最好不要含空格：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/pcl9.png" alt=""></p><p>安装过程中可能会提示路径过长，忽略即可。安装完后，打开安装目录下的<code>3rdParty\OpenNI2</code>目录，双击<code>OpenNI-Windows-x64-2.2.msi</code>，先点击<code>remove</code>删除已安装的OpenNI，删除后再次打开该安装文件，安装目录选择<code>D:\PCL-1.11.0\3rdParty\OpenNI2</code>。安装完毕后，将之前下载的<code>.zip</code>文件的所有内容解压到<code>D:\PCL-1.11.0\bin</code>下。</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/pcl10.png" alt=""></p><h3 id="添加环境变量"><a href="#添加环境变量" class="headerlink" title="添加环境变量"></a>添加环境变量</h3><p>编辑用户的Path环境变量，添加以下内容：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/pcl11.png" alt=""></p><h3 id="配置Visual-Studio"><a href="#配置Visual-Studio" class="headerlink" title="配置Visual Studio"></a>配置Visual Studio</h3><h4 id="项目属性配置"><a href="#项目属性配置" class="headerlink" title="项目属性配置"></a>项目属性配置</h4><p>打开Visual Studio，创建空项目，选择编译环境为64位，Debug版本：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/pcl13.png" alt=""></p><p>选择菜单栏的“项目”—“属性”，设置调试环境为:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">PATH=D:\PCL-1.11.0\\bin;D:\PCL-1.11.0\\3rdParty\FLANN\bin;D:\PCL-1.11.0\\3rdParty\VTK\bin;D:\PCL-1.11.0\\3rdParty\OpenNI2\Tools</span><br></pre></td></tr></table></figure><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/pcl29.png" alt=""></p><p>接着选择“C/C++”—“语言”，将符合模式设置为否：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/pcl27.png" alt=""></p><p>还要在所有选项中将SDL检查设置为否：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/pcl28.png" alt=""></p><h4 id="属性表配置"><a href="#属性表配置" class="headerlink" title="属性表配置"></a>属性表配置</h4><p>选择菜单栏的“视图”—“其他窗口”—“属性管理器”，在<code>Debug | 64</code>下新建项目属性表，命名为<code>PCL 1.11.0 Debug x64.props</code>：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/pcl14.png" alt=""></p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/pcl15.png" alt=""></p><p>创建完后，右键点击该属性表，选择属性：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/pcl16.png" alt=""></p><p>在C/C++的所有选项中，将SDL检查设置为否：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/pcl23.png" alt=""></p><p>接着配置Include目录：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/pcl17.png" alt=""></p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/pcl18.png" alt=""></p><p>紧接着配置库目录：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/pcl19.png" alt=""></p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/pcl20.png" alt=""></p><p>配置预处理器：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/pcl21.png" alt=""></p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/pcl22.png" alt=""></p><p>接下来配置附加依赖项：</p><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/pcl24.png" alt=""></p><p>Debug版本：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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></pre></td><td class="code"><pre><span class="line">pcl_kdtreed.lib</span><br><span class="line">pcl_stereod.lib</span><br><span class="line">pcl_commond.lib</span><br><span class="line">pcl_recognitiond.lib</span><br><span class="line">pcl_keypointsd.lib</span><br><span class="line">pcl_surfaced.lib</span><br><span class="line">pcl_featuresd.lib</span><br><span class="line">pcl_registrationd.lib</span><br><span class="line">pcl_mld.lib</span><br><span class="line">pcl_trackingd.lib</span><br><span class="line">pcl_filtersd.lib</span><br><span class="line">pcl_sample_consensusd.lib</span><br><span class="line">pcl_octreed.lib</span><br><span class="line">pcl_visualizationd.lib</span><br><span class="line">pcl_searchd.lib</span><br><span class="line">pcl_io_plyd.lib</span><br><span class="line">pcl_outofcored.lib</span><br><span class="line">pcl_iod.lib</span><br><span class="line">pcl_segmentationd.lib</span><br><span class="line">pcl_peopled.lib</span><br><span class="line">vtkChartsCore-8.2-gd.lib</span><br><span class="line">vtkCommonColor-8.2-gd.lib</span><br><span class="line">vtkCommonComputationalGeometry-8.2-gd.lib</span><br><span class="line">vtkCommonCore-8.2-gd.lib</span><br><span class="line">vtkCommonDataModel-8.2-gd.lib</span><br><span class="line">vtkCommonExecutionModel-8.2-gd.lib</span><br><span class="line">vtkCommonMath-8.2-gd.lib</span><br><span class="line">vtkCommonMisc-8.2-gd.lib</span><br><span class="line">vtkCommonSystem-8.2-gd.lib</span><br><span class="line">vtkCommonTransforms-8.2-gd.lib</span><br><span class="line">vtkDICOMParser-8.2-gd.lib</span><br><span class="line">vtkDomainsChemistry-8.2-gd.lib</span><br><span class="line">vtkDomainsChemistryOpenGL2-8.2-gd.lib</span><br><span class="line">vtkdoubleconversion-8.2-gd.lib</span><br><span class="line">vtkexodusII-8.2-gd.lib</span><br><span class="line">vtkexpat-8.2-gd.lib</span><br><span class="line">vtkFiltersAMR-8.2-gd.lib</span><br><span class="line">vtkFiltersCore-8.2-gd.lib</span><br><span class="line">vtkFiltersExtraction-8.2-gd.lib</span><br><span class="line">vtkFiltersFlowPaths-8.2-gd.lib</span><br><span class="line">vtkFiltersGeneral-8.2-gd.lib</span><br><span class="line">vtkFiltersGeneric-8.2-gd.lib</span><br><span class="line">vtkFiltersGeometry-8.2-gd.lib</span><br><span class="line">vtkFiltersHybrid-8.2-gd.lib</span><br><span class="line">vtkFiltersHyperTree-8.2-gd.lib</span><br><span class="line">vtkFiltersImaging-8.2-gd.lib</span><br><span class="line">vtkFiltersModeling-8.2-gd.lib</span><br><span class="line">vtkFiltersParallel-8.2-gd.lib</span><br><span class="line">vtkFiltersParallelImaging-8.2-gd.lib</span><br><span class="line">vtkFiltersPoints-8.2-gd.lib</span><br><span class="line">vtkFiltersProgrammable-8.2-gd.lib</span><br><span class="line">vtkFiltersSelection-8.2-gd.lib</span><br><span class="line">vtkFiltersSMP-8.2-gd.lib</span><br><span class="line">vtkFiltersSources-8.2-gd.lib</span><br><span class="line">vtkFiltersStatistics-8.2-gd.lib</span><br><span class="line">vtkFiltersTexture-8.2-gd.lib</span><br><span class="line">vtkFiltersTopology-8.2-gd.lib</span><br><span class="line">vtkFiltersVerdict-8.2-gd.lib</span><br><span class="line">vtkfreetype-8.2-gd.lib</span><br><span class="line">vtkGeovisCore-8.2-gd.lib</span><br><span class="line">vtkgl2ps-8.2-gd.lib</span><br><span class="line">vtkglew-8.2-gd.lib</span><br><span class="line">vtkGUISupportMFC-8.2-gd.lib</span><br><span class="line">vtkhdf5-8.2-gd.lib</span><br><span class="line">vtkhdf5_hl-8.2-gd.lib</span><br><span class="line">vtkImagingColor-8.2-gd.lib</span><br><span class="line">vtkImagingCore-8.2-gd.lib</span><br><span class="line">vtkImagingFourier-8.2-gd.lib</span><br><span class="line">vtkImagingGeneral-8.2-gd.lib</span><br><span class="line">vtkImagingHybrid-8.2-gd.lib</span><br><span class="line">vtkImagingMath-8.2-gd.lib</span><br><span class="line">vtkImagingMorphological-8.2-gd.lib</span><br><span class="line">vtkImagingSources-8.2-gd.lib</span><br><span class="line">vtkImagingStatistics-8.2-gd.lib</span><br><span class="line">vtkImagingStencil-8.2-gd.lib</span><br><span class="line">vtkInfovisCore-8.2-gd.lib</span><br><span class="line">vtkInfovisLayout-8.2-gd.lib</span><br><span class="line">vtkInteractionImage-8.2-gd.lib</span><br><span class="line">vtkInteractionStyle-8.2-gd.lib</span><br><span class="line">vtkInteractionWidgets-8.2-gd.lib</span><br><span class="line">vtkIOAMR-8.2-gd.lib</span><br><span class="line">vtkIOAsynchronous-8.2-gd.lib</span><br><span class="line">vtkIOCityGML-8.2-gd.lib</span><br><span class="line">vtkIOCore-8.2-gd.lib</span><br><span class="line">vtkIOEnSight-8.2-gd.lib</span><br><span class="line">vtkIOExodus-8.2-gd.lib</span><br><span class="line">vtkIOExport-8.2-gd.lib</span><br><span class="line">vtkIOExportOpenGL2-8.2-gd.lib</span><br><span class="line">vtkIOExportPDF-8.2-gd.lib</span><br><span class="line">vtkIOGeometry-8.2-gd.lib</span><br><span class="line">vtkIOImage-8.2-gd.lib</span><br><span class="line">vtkIOImport-8.2-gd.lib</span><br><span class="line">vtkIOInfovis-8.2-gd.lib</span><br><span class="line">vtkIOLegacy-8.2-gd.lib</span><br><span class="line">vtkIOLSDyna-8.2-gd.lib</span><br><span class="line">vtkIOMINC-8.2-gd.lib</span><br><span class="line">vtkIOMovie-8.2-gd.lib</span><br><span class="line">vtkIONetCDF-8.2-gd.lib</span><br><span class="line">vtkIOParallel-8.2-gd.lib</span><br><span class="line">vtkIOParallelXML-8.2-gd.lib</span><br><span class="line">vtkIOPLY-8.2-gd.lib</span><br><span class="line">vtkIOSegY-8.2-gd.lib</span><br><span class="line">vtkIOSQL-8.2-gd.lib</span><br><span class="line">vtkIOTecplotTable-8.2-gd.lib</span><br><span class="line">vtkIOVeraOut-8.2-gd.lib</span><br><span class="line">vtkIOVideo-8.2-gd.lib</span><br><span class="line">vtkIOXML-8.2-gd.lib</span><br><span class="line">vtkIOXMLParser-8.2-gd.lib</span><br><span class="line">vtkjpeg-8.2-gd.lib</span><br><span class="line">vtkjsoncpp-8.2-gd.lib</span><br><span class="line">vtklibharu-8.2-gd.lib</span><br><span class="line">vtklibxml2-8.2-gd.lib</span><br><span class="line">vtklz4-8.2-gd.lib</span><br><span class="line">vtklzma-8.2-gd.lib</span><br><span class="line">vtkmetaio-8.2-gd.lib</span><br><span class="line">vtkNetCDF-8.2-gd.lib</span><br><span class="line">vtkogg-8.2-gd.lib</span><br><span class="line">vtkParallelCore-8.2-gd.lib</span><br><span class="line">vtkpng-8.2-gd.lib</span><br><span class="line">vtkproj-8.2-gd.lib</span><br><span class="line">vtkpugixml-8.2-gd.lib</span><br><span class="line">vtkRenderingAnnotation-8.2-gd.lib</span><br><span class="line">vtkRenderingContext2D-8.2-gd.lib</span><br><span class="line">vtkRenderingContextOpenGL2-8.2-gd.lib</span><br><span class="line">vtkRenderingCore-8.2-gd.lib</span><br><span class="line">vtkRenderingExternal-8.2-gd.lib</span><br><span class="line">vtkRenderingFreeType-8.2-gd.lib</span><br><span class="line">vtkRenderingGL2PSOpenGL2-8.2-gd.lib</span><br><span class="line">vtkRenderingImage-8.2-gd.lib</span><br><span class="line">vtkRenderingLabel-8.2-gd.lib</span><br><span class="line">vtkRenderingLOD-8.2-gd.lib</span><br><span class="line">vtkRenderingOpenGL2-8.2-gd.lib</span><br><span class="line">vtkRenderingVolume-8.2-gd.lib</span><br><span class="line">vtkRenderingVolumeOpenGL2-8.2-gd.lib</span><br><span class="line">vtksqlite-8.2-gd.lib</span><br><span class="line">vtksys-8.2-gd.lib</span><br><span class="line">vtktheora-8.2-gd.lib</span><br><span class="line">vtktiff-8.2-gd.lib</span><br><span class="line">vtkverdict-8.2-gd.lib</span><br><span class="line">vtkViewsContext2D-8.2-gd.lib</span><br><span class="line">vtkViewsCore-8.2-gd.lib</span><br><span class="line">vtkViewsInfovis-8.2-gd.lib</span><br><span class="line">vtkzlib-8.2-gd.lib</span><br><span class="line">flann-gd.lib</span><br><span class="line">flann_cpp-gd.lib</span><br><span class="line">flann_cpp_s-gd.lib</span><br><span class="line">flann_s-gd.lib</span><br></pre></td></tr></table></figure><h3 id="测试环境"><a href="#测试环境" class="headerlink" title="测试环境"></a>测试环境</h3><h4 id="编写代码"><a href="#编写代码" class="headerlink" title="编写代码"></a>编写代码</h4><p>这里我们写上一段测试代码：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;vector&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;ctime&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pcl/point_cloud.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pcl/octree/octree.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;boost/thread/thread.hpp&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;pcl/visualization/pcl_visualizer.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="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">char</span>** argv)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="built_in">srand</span>((<span class="keyword">unsigned</span> <span class="keyword">int</span>)<span class="built_in">time</span>(<span class="literal">NULL</span>));</span><br><span class="line">pcl::PointCloud&lt;pcl::PointXYZ&gt;::<span class="function">Ptr <span class="title">cloud</span><span class="params">(<span class="keyword">new</span> pcl::PointCloud&lt;pcl::PointXYZ&gt;)</span></span>;</span><br><span class="line"></span><br><span class="line">cloud-&gt;width = <span class="number">1000</span>;</span><br><span class="line">cloud-&gt;height = <span class="number">1</span>;</span><br><span class="line">cloud-&gt;points.<span class="built_in">resize</span>(cloud-&gt;width * cloud-&gt;height);</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">size_t</span> i = <span class="number">0</span>; i &lt; cloud-&gt;points.<span class="built_in">size</span>(); ++i)</span><br><span class="line">&#123;</span><br><span class="line">cloud-&gt;points[i].x = <span class="number">1024.0f</span> * <span class="built_in">rand</span>() / (RAND_MAX + <span class="number">1.0f</span>);</span><br><span class="line">cloud-&gt;points[i].y = <span class="number">1024.0f</span> * <span class="built_in">rand</span>() / (RAND_MAX + <span class="number">1.0f</span>);</span><br><span class="line">cloud-&gt;points[i].z = <span class="number">1024.0f</span> * <span class="built_in">rand</span>() / (RAND_MAX + <span class="number">1.0f</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">pcl::<span class="function">octree::OctreePointCloudSearch&lt;pcl::PointXYZ&gt; <span class="title">octree</span><span class="params">(<span class="number">0.1</span>)</span></span>;</span><br><span class="line">octree.<span class="built_in">setInputCloud</span>(cloud);</span><br><span class="line">octree.<span class="built_in">addPointsFromInputCloud</span>();</span><br><span class="line">pcl::PointXYZ searchPoint;</span><br><span class="line">searchPoint.x = <span class="number">1024.0f</span> * <span class="built_in">rand</span>() / (RAND_MAX + <span class="number">1.0f</span>);</span><br><span class="line">searchPoint.y = <span class="number">1024.0f</span> * <span class="built_in">rand</span>() / (RAND_MAX + <span class="number">1.0f</span>);</span><br><span class="line">searchPoint.z = <span class="number">1024.0f</span> * <span class="built_in">rand</span>() / (RAND_MAX + <span class="number">1.0f</span>);</span><br><span class="line"></span><br><span class="line">vector&lt;<span class="keyword">int</span>&gt;pointIdxRadiusSearch;</span><br><span class="line">vector&lt;<span class="keyword">float</span>&gt;pointRadiusSquaredDistance;</span><br><span class="line"><span class="keyword">float</span> radius = <span class="number">256.0f</span> * <span class="built_in">rand</span>() / (RAND_MAX + <span class="number">1.0f</span>);</span><br><span class="line">cout &lt;&lt; <span class="string">&quot;Neighbors within radius search at (&quot;</span> &lt;&lt; searchPoint.x</span><br><span class="line">&lt;&lt; <span class="string">&quot; &quot;</span> &lt;&lt; searchPoint.y</span><br><span class="line">&lt;&lt; <span class="string">&quot; &quot;</span> &lt;&lt; searchPoint.z</span><br><span class="line">&lt;&lt; <span class="string">&quot;) with radius=&quot;</span> &lt;&lt; radius &lt;&lt; endl;</span><br><span class="line"><span class="keyword">if</span> (octree.<span class="built_in">radiusSearch</span>(searchPoint, radius, pointIdxRadiusSearch, pointRadiusSquaredDistance) &gt; <span class="number">0</span>)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">size_t</span> i = <span class="number">0</span>; i &lt; pointIdxRadiusSearch.<span class="built_in">size</span>(); ++i)</span><br><span class="line">cout &lt;&lt; <span class="string">&quot;    &quot;</span> &lt;&lt; cloud-&gt;points[pointIdxRadiusSearch[i]].x</span><br><span class="line">&lt;&lt; <span class="string">&quot; &quot;</span> &lt;&lt; cloud-&gt;points[pointIdxRadiusSearch[i]].y</span><br><span class="line">&lt;&lt; <span class="string">&quot; &quot;</span> &lt;&lt; cloud-&gt;points[pointIdxRadiusSearch[i]].z</span><br><span class="line">&lt;&lt; <span class="string">&quot; (squared distance: &quot;</span> &lt;&lt; pointRadiusSquaredDistance[i] &lt;&lt; <span class="string">&quot;)&quot;</span> &lt;&lt; endl;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">boost::shared_ptr&lt;pcl::visualization::PCLVisualizer&gt;<span class="built_in">viewer</span>(<span class="keyword">new</span> pcl::visualization::<span class="built_in">PCLVisualizer</span>(<span class="string">&quot;显示点云&quot;</span>));</span><br><span class="line">viewer-&gt;<span class="built_in">setBackgroundColor</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">pcl::visualization::PointCloudColorHandlerCustom&lt;pcl::PointXYZ&gt;<span class="built_in">target_color</span>(cloud, <span class="number">255</span>, <span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line">viewer-&gt;addPointCloud&lt;pcl::PointXYZ&gt;(cloud, target_color, <span class="string">&quot;target cloud&quot;</span>);</span><br><span class="line">viewer-&gt;<span class="built_in">setPointCloudRenderingProperties</span>(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, <span class="number">1</span>, <span class="string">&quot;target cloud&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> (!viewer-&gt;<span class="built_in">wasStopped</span>())</span><br><span class="line">&#123;</span><br><span class="line">viewer-&gt;<span class="built_in">spinOnce</span>(<span class="number">100</span>);</span><br><span class="line">boost::this_thread::<span class="built_in">sleep</span>(boost::posix_time::<span class="built_in">microseconds</span>(<span class="number">1000</span>));</span><br><span class="line">&#125;</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><h4 id="Debug运行"><a href="#Debug运行" class="headerlink" title="Debug运行"></a>Debug运行</h4><p><img src="https://cdn.jsdelivr.net/gh/qinglew/ImageHost/pcl30.png" alt=""></p><h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>以上就是在Windows 10操作系统下，为Visual Studio 2019配置点云库PCL 1.11.0的过程。当然，上面用的是debug版本，也可以换成release版本，只要附加依赖的内容换成release的内容即可，这里给出release版本的附加依赖的内容：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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></pre></td><td class="code"><pre><span class="line">pcl_kdtreed.lib</span><br><span class="line">pcl_stereod.lib</span><br><span class="line">pcl_commond.lib</span><br><span class="line">pcl_recognitiond.lib</span><br><span class="line">pcl_keypointsd.lib</span><br><span class="line">pcl_surfaced.lib</span><br><span class="line">pcl_featuresd.lib</span><br><span class="line">pcl_registrationd.lib</span><br><span class="line">pcl_mld.lib</span><br><span class="line">pcl_trackingd.lib</span><br><span class="line">pcl_filtersd.lib</span><br><span class="line">pcl_sample_consensusd.lib</span><br><span class="line">pcl_octreed.lib</span><br><span class="line">pcl_visualizationd.lib</span><br><span class="line">pcl_searchd.lib</span><br><span class="line">pcl_io_plyd.lib</span><br><span class="line">pcl_outofcored.lib</span><br><span class="line">pcl_iod.lib</span><br><span class="line">pcl_segmentationd.lib</span><br><span class="line">pcl_peopled.lib</span><br><span class="line">vtkChartsCore-8.2.lib</span><br><span class="line">vtkCommonColor-8.2.lib</span><br><span class="line">vtkCommonComputationalGeometry-8.2.lib</span><br><span class="line">vtkCommonCore-8.2.lib</span><br><span class="line">vtkCommonDataModel-8.2.lib</span><br><span class="line">vtkCommonExecutionModel-8.2.lib</span><br><span class="line">vtkCommonMath-8.2.lib</span><br><span class="line">vtkCommonMisc-8.2.lib</span><br><span class="line">vtkCommonSystem-8.2.lib</span><br><span class="line">vtkCommonTransforms-8.2.lib</span><br><span class="line">vtkDICOMParser-8.2.lib</span><br><span class="line">vtkDomainsChemistry-8.2.lib</span><br><span class="line">vtkDomainsChemistryOpenGL2-8.2.lib</span><br><span class="line">vtkdoubleconversion-8.2.lib</span><br><span class="line">vtkexodusII-8.2.lib</span><br><span class="line">vtkexpat-8.2.lib</span><br><span class="line">vtkFiltersAMR-8.2.lib</span><br><span class="line">vtkFiltersCore-8.2.lib</span><br><span class="line">vtkFiltersExtraction-8.2.lib</span><br><span class="line">vtkFiltersFlowPaths-8.2.lib</span><br><span class="line">vtkFiltersGeneral-8.2.lib</span><br><span class="line">vtkFiltersGeneric-8.2.lib</span><br><span class="line">vtkFiltersGeometry-8.2.lib</span><br><span class="line">vtkFiltersHybrid-8.2.lib</span><br><span class="line">vtkFiltersHyperTree-8.2.lib</span><br><span class="line">vtkFiltersImaging-8.2.lib</span><br><span class="line">vtkFiltersModeling-8.2.lib</span><br><span class="line">vtkFiltersParallel-8.2.lib</span><br><span class="line">vtkFiltersParallelImaging-8.2.lib</span><br><span class="line">vtkFiltersPoints-8.2.lib</span><br><span class="line">vtkFiltersProgrammable-8.2.lib</span><br><span class="line">vtkFiltersSelection-8.2.lib</span><br><span class="line">vtkFiltersSMP-8.2.lib</span><br><span class="line">vtkFiltersSources-8.2.lib</span><br><span class="line">vtkFiltersStatistics-8.2.lib</span><br><span class="line">vtkFiltersTexture-8.2.lib</span><br><span class="line">vtkFiltersTopology-8.2.lib</span><br><span class="line">vtkFiltersVerdict-8.2.lib</span><br><span class="line">vtkfreetype-8.2.lib</span><br><span class="line">vtkGeovisCore-8.2.lib</span><br><span class="line">vtkgl2ps-8.2.lib</span><br><span class="line">vtkglew-8.2.lib</span><br><span class="line">vtkGUISupportMFC-8.2.lib</span><br><span class="line">vtkhdf5-8.2.lib</span><br><span class="line">vtkhdf5_hl-8.2.lib</span><br><span class="line">vtkImagingColor-8.2.lib</span><br><span class="line">vtkImagingCore-8.2.lib</span><br><span class="line">vtkImagingFourier-8.2.lib</span><br><span class="line">vtkImagingGeneral-8.2.lib</span><br><span class="line">vtkImagingHybrid-8.2.lib</span><br><span class="line">vtkImagingMath-8.2.lib</span><br><span class="line">vtkImagingMorphological-8.2.lib</span><br><span class="line">vtkImagingSources-8.2.lib</span><br><span class="line">vtkImagingStatistics-8.2.lib</span><br><span class="line">vtkImagingStencil-8.2.lib</span><br><span class="line">vtkInfovisCore-8.2.lib</span><br><span class="line">vtkInfovisLayout-8.2.lib</span><br><span class="line">vtkInteractionImage-8.2.lib</span><br><span class="line">vtkInteractionStyle-8.2.lib</span><br><span class="line">vtkInteractionWidgets-8.2.lib</span><br><span class="line">vtkIOAMR-8.2.lib</span><br><span class="line">vtkIOAsynchronous-8.2.lib</span><br><span class="line">vtkIOCityGML-8.2.lib</span><br><span class="line">vtkIOCore-8.2.lib</span><br><span class="line">vtkIOEnSight-8.2.lib</span><br><span class="line">vtkIOExodus-8.2.lib</span><br><span class="line">vtkIOExport-8.2.lib</span><br><span class="line">vtkIOExportOpenGL2-8.2.lib</span><br><span class="line">vtkIOExportPDF-8.2.lib</span><br><span class="line">vtkIOGeometry-8.2.lib</span><br><span class="line">vtkIOImage-8.2.lib</span><br><span class="line">vtkIOImport-8.2.lib</span><br><span class="line">vtkIOInfovis-8.2.lib</span><br><span class="line">vtkIOLegacy-8.2.lib</span><br><span class="line">vtkIOLSDyna-8.2.lib</span><br><span class="line">vtkIOMINC-8.2.lib</span><br><span class="line">vtkIOMovie-8.2.lib</span><br><span class="line">vtkIONetCDF-8.2.lib</span><br><span class="line">vtkIOParallel-8.2.lib</span><br><span class="line">vtkIOParallelXML-8.2.lib</span><br><span class="line">vtkIOPLY-8.2.lib</span><br><span class="line">vtkIOSegY-8.2.lib</span><br><span class="line">vtkIOSQL-8.2.lib</span><br><span class="line">vtkIOTecplotTable-8.2.lib</span><br><span class="line">vtkIOVeraOut-8.2.lib</span><br><span class="line">vtkIOVideo-8.2.lib</span><br><span class="line">vtkIOXML-8.2.lib</span><br><span class="line">vtkIOXMLParser-8.2.lib</span><br><span class="line">vtkjpeg-8.2.lib</span><br><span class="line">vtkjsoncpp-8.2.lib</span><br><span class="line">vtklibharu-8.2.lib</span><br><span class="line">vtklibxml2-8.2.lib</span><br><span class="line">vtklz4-8.2.lib</span><br><span class="line">vtklzma-8.2.lib</span><br><span class="line">vtkmetaio-8.2.lib</span><br><span class="line">vtkNetCDF-8.2.lib</span><br><span class="line">vtkogg-8.2.lib</span><br><span class="line">vtkParallelCore-8.2.lib</span><br><span class="line">vtkpng-8.2.lib</span><br><span class="line">vtkproj-8.2.lib</span><br><span class="line">vtkpugixml-8.2.lib</span><br><span class="line">vtkRenderingAnnotation-8.2.lib</span><br><span class="line">vtkRenderingContext2D-8.2.lib</span><br><span class="line">vtkRenderingContextOpenGL2-8.2.lib</span><br><span class="line">vtkRenderingCore-8.2.lib</span><br><span class="line">vtkRenderingExternal-8.2.lib</span><br><span class="line">vtkRenderingFreeType-8.2.lib</span><br><span class="line">vtkRenderingGL2PSOpenGL2-8.2.lib</span><br><span class="line">vtkRenderingImage-8.2.lib</span><br><span class="line">vtkRenderingLabel-8.2.lib</span><br><span class="line">vtkRenderingLOD-8.2.lib</span><br><span class="line">vtkRenderingOpenGL2-8.2.lib</span><br><span class="line">vtkRenderingVolume-8.2.lib</span><br><span class="line">vtkRenderingVolumeOpenGL2-8.2.lib</span><br><span class="line">vtksqlite-8.2.lib</span><br><span class="line">vtksys-8.2.lib</span><br><span class="line">vtktheora-8.2.lib</span><br><span class="line">vtktiff-8.2.lib</span><br><span class="line">vtkverdict-8.2.lib</span><br><span class="line">vtkViewsContext2D-8.2.lib</span><br><span class="line">vtkViewsCore-8.2.lib</span><br><span class="line">vtkViewsInfovis-8.2.lib</span><br><span class="line">vtkzlib-8.2.lib</span><br><span class="line">flann.lib</span><br><span class="line">flann_cpp.lib</span><br><span class="line">flann_cpp_s.lib</span><br><span class="line">flann_s.lib</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;下载安装PCL&quot;&gt;&lt;a href=&quot;#下载安装PCL&quot; class=&quot;headerlink&quot; title=&quot;下载安装PCL&quot;&gt;&lt;/a&gt;下载安装PCL&lt;/h3&gt;&lt;p&gt;访问PCL的&lt;a class=&quot;link&quot;   href=&quot;https://github.com/P</summary>
      
    
    
    
    <category term="环境搭建" scheme="http://example.com/categories/%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/"/>
    
    
    <category term="Visual Studio" scheme="http://example.com/tags/Visual-Studio/"/>
    
    <category term="PCL" scheme="http://example.com/tags/PCL/"/>
    
  </entry>
  
</feed>
