<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/scripts/pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:h="http://www.w3.org/TR/html4/"><channel><title>zh ink</title><description>Stay hungry, stay foolish</description><link>https://astro-pure.js.org</link><item><title>Lec17 Virtual memory for applications</title><link>https://astro-pure.js.org/blog/mit6s081_lecture17/lec17-virtual-memory-for-applications</link><guid isPermaLink="true">https://astro-pure.js.org/blog/mit6s081_lecture17/lec17-virtual-memory-for-applications</guid><description>MIT6.S081 Lecture17 Virtual memory for applications学习笔记</description><pubDate>Fri, 12 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1.应用程序使用虚拟内存所需要的特性&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;需要支持trap：使内核中的Page Fault可以传播到用户空间中，用户空间中的handler处理相应Page Fault,之后返回内核恢复指令执行；&lt;/li&gt;
&lt;li&gt;Prot1:降低一个内存Page的读写权限(accessibility)，如从可读写变为只读权限；&lt;/li&gt;
&lt;li&gt;ProtN：等效调用N次Prot1，但是使用ProtN可以平摊修改TLE的成本（一次可以修改多位），而不是像多次调用那样每次都需要修改TLE；&lt;/li&gt;
&lt;li&gt;Unprot：提升一个内存Page的读写权限(accessibility)；&lt;/li&gt;
&lt;li&gt;Dirty：查看某个内存Page是否被修改过（Dirty）；&lt;/li&gt;
&lt;li&gt;map2：将一段物理内存映射到两份虚拟内存上，即两个虚拟内存地址指向同一个物理内存，且两份虚拟内存有不同的读写权限（accessibility）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2.支持应用程序使用虚拟内存的系统调用&lt;/h2&gt;
&lt;h3&gt;(1)mmap&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;mmap, munmap - map or unmap files or devices into memory
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;void *mmap(size_t length;
           void addr[length], size_t length, int prot, int flags,
           int fd, off_t offset);
int munmap(size_t length;
           void addr[length], size_t length);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;addr：想要映射到的目标地址，若为null，则内核选择一个地址完成映射&lt;/li&gt;
&lt;li&gt;length：地址段长度&lt;/li&gt;
&lt;li&gt;prot：读写权限设置位（Protection bit）&lt;/li&gt;
&lt;li&gt;flags：指定不同的效果&lt;/li&gt;
&lt;li&gt;fd：传入的对象（文件描述符）&lt;/li&gt;
&lt;li&gt;offset：文件中的偏移量&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;函数作用：将文件中从起始位置+offset的地方开始，映射到指定的目标地址，长度为length，实现把文件中的内容加载到内存地址空间中。&lt;/p&gt;
&lt;h3&gt;(2)mprotect&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;int mprotect(size_t size;
             void addr[size], size_t size, int prot);

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;函数作用：对虚拟内存设置权限。&lt;/p&gt;
&lt;h3&gt;(3)sigaction&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;int sigaction(int signum,
              const struct sigaction *_Nullable restrict act,
              struct sigaction *_Nullable restrict oldact);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;具体用法可查看：&lt;a href=&quot;https://man7.org/linux/man-pages/man2/sigaction.2.html&quot;&gt;sigaction(2) - Linux manual page&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.runoob.com/cprogramming/c-function-sigaction.html&quot;&gt;C 库函数 – sigaction() | 菜鸟教程&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;函数作用：当特定的signal发生时，可以调用预先设定好的handler，在handler中响应事件。&lt;/p&gt;
&lt;h3&gt;(4)系统调用与特性之间的关系&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;trap对应的是sigaction系统调用。&lt;/li&gt;
&lt;li&gt;Prot1，ProtN和Unprot可以使用mprotect系统调用来实现。mprotect足够的灵活，你可以用它来修改一个Page的权限，也可以用它来修改多个Page的权限。当修改多个Page的权限时，可以获得只清除一次TLB的好处。&lt;/li&gt;
&lt;li&gt;查看Page的Dirty位要稍微复杂点，并没有一个直接的系统调用实现这个特性，不过你可以使用一些技巧完成它，我稍后会介绍它。&lt;/li&gt;
&lt;li&gt;map2也没有一个系统调用能直接对应它，通过多次调用mmap，你可以实现map2特性。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3.虚拟内存系统如何支持用户应用程序&lt;/h2&gt;
&lt;h3&gt;(1)VMAs&lt;/h3&gt;
&lt;p&gt;VMAs记录的是一些连续内存地址段（section）的信息。在地址空间中，包含多个section，每个section又包含了一段连续的地址段。&lt;strong&gt;每个section对应一个VMA对象。每个section都有相同的权限，并且对应到同一个VMA对象。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;(2)User level trap&lt;/h3&gt;
&lt;p&gt;流程：segfault事件发生--&gt;传播到user space--&gt;通过用户空间的一个upcall运行handler--&gt;在handler中处理事件--&gt;返回内核代码--&gt; 从内核返回之前被中断的进程&lt;/p&gt;
&lt;h2&gt;4.构建大的缓存表&lt;/h2&gt;
&lt;p&gt;**要求：**从0~n，表单中记录了一些费时的函数计算结果，可以通过参数i查询对应的结果。需要预先计算好结果并存储在表单中供多次查询使用，表单很大，但是只可以用一个page的物理内存！&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;实现思路：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;分配一个大的虚拟地址段，但是不分配任何物理内存&lt;/li&gt;
&lt;li&gt;查询参数i对应的结果，导致pagefault&lt;/li&gt;
&lt;li&gt;发生pagefault后，针对访问目标虚拟地址分配物理内存page，并计算这个page上所有参数对应的结果&lt;/li&gt;
&lt;li&gt;消耗完物理内存后回收物理内存page并分配给新的虚拟内存地址段&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;5.Baker&apos;s Real-Time Copying Garbage Collector&lt;/h2&gt;
&lt;p&gt;Garbage Collector可以实现自动释放内存的功能，GC可以利用用户空间虚拟内特性。&lt;/p&gt;
&lt;h3&gt;Coping GC&lt;/h3&gt;
&lt;p&gt;内存空间分为：from空间；to空间&lt;/p&gt;
&lt;p&gt;空闲时先从from空间申请内存，假设申请了一个类似树的数据结构。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./1.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Coping GC的主要思想是将正在使用的对象拷贝到to空间去，从根节点(root)开始拷贝（假设只有一个根节点），递归遍历并复制所有可达对象到to空间，同时在每个可达对象的from空间原地址留下forwarding pointer，记录对象已迁移的目标地址。直到from空间变为空闲区域！&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;Baker增量GC&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;不暂停程序&lt;/strong&gt;，将 GC 拆分为多个小步骤：
&lt;ul&gt;
&lt;li&gt;初始：仅复制根对象（root）到 to 空间（&lt;strong&gt;未更新其内部指针&lt;/strong&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;每次内存分配（&lt;code&gt;new&lt;/code&gt;）时&lt;/strong&gt;：GC 扫描并复制若干对象。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;每次指针解引用（dereference）时&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;检查目标对象是否仍在 from 空间；&lt;/li&gt;
&lt;li&gt;若是，则立即 &lt;strong&gt;forward 到 to 空间&lt;/strong&gt;，并更新指针。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;确保运行时所有活跃指针最终都指向 to 空间对象。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;dereference需要额外的步骤，增加开销&lt;/li&gt;
&lt;li&gt;不容易并行运行GC&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;6.使用虚拟内存特性的GC&lt;/h2&gt;
&lt;p&gt;基本思想：&lt;/p&gt;
&lt;h3&gt;(1)地址划分&lt;/h3&gt;
&lt;p&gt;每个半空间（from / to）进一步分为两个逻辑区域：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;unscanned&lt;/strong&gt;：对象已复制但未扫描（指针未更新）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;scanned&lt;/strong&gt;：对象已完成扫描（指针已修正，可安全访问）&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;(2)权限控制+pagefault&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;unscanned 区域的页权限设为 &lt;code&gt;None&lt;/code&gt;（不可访问）&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;应用程序首次访问 → 触发 &lt;strong&gt;Page Fault&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Page Fault Handler 中&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;GC 扫描该页内所有对象&lt;/li&gt;
&lt;li&gt;将其指针指向的对象从 from → to 空间 &lt;strong&gt;forward&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;将该页标记为 &lt;strong&gt;scanned&lt;/strong&gt;，并通过 &lt;code&gt;mprotect&lt;/code&gt; 恢复读写权限&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;此后应用可安全访问，无需运行时指针检查&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;(3)并发安全机制&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;应用程序只能访问scanned页面（即unscanned页面权限设置为None）&lt;/li&gt;
&lt;li&gt;但是GC又能够读写unscanned页面（即unscanned页面权限设置为RW）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;所以如何实现unscannned页面对于不同的进程有不同的权限呢？map2！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;即将同一物理地址空间页映射到两份不同的虚拟地址空间段上，具有不同的权限。&lt;/p&gt;
&lt;h2&gt;7.虚拟内存特性的GC代码展示&lt;/h2&gt;
&lt;p&gt;使用api：&lt;code&gt;new&lt;/code&gt;和&lt;code&gt;readptr&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;readptr会检查指针是否在from空间，如果在from空间，则把它指向对象拷贝到to空间中。&lt;/p&gt;
&lt;h3&gt;1. 内存布局设置（初始化阶段）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;使用 &lt;code&gt;shm_open&lt;/code&gt; 创建一个匿名共享内存对象（本质是 POSIX 共享内存段）。&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;ftruncate&lt;/code&gt; 将其扩展到足够容纳 from-space + to-space 的大小。&lt;/li&gt;
&lt;li&gt;通过 &lt;strong&gt;两次 &lt;code&gt;mmap&lt;/code&gt;&lt;/strong&gt; 将同一物理内存映射到两个不同的虚拟地址区间：
&lt;ul&gt;
&lt;li&gt;一次映射给 &lt;strong&gt;mutator&lt;/strong&gt;（应用程序视角）；&lt;/li&gt;
&lt;li&gt;一次映射给 &lt;strong&gt;GC&lt;/strong&gt;（GC 视角，拥有完整读写权限）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./2.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;2. 初始状态：to-space 对 mutator &lt;strong&gt;不可访问&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;在 &lt;code&gt;flip()&lt;/code&gt;（切换 from/to 空间）时，GC 调用 &lt;code&gt;mprotect(to_region, PROT_NONE)&lt;/code&gt;，使得 mutator 无法访问 to-space 中的任何页。&lt;/li&gt;
&lt;li&gt;GC 将根对象（如 &lt;code&gt;root_head&lt;/code&gt;, &lt;code&gt;root_last&lt;/code&gt;）复制到 to-space —— &lt;strong&gt;但 mutator 不能访问它们&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;下面代码中的to是应用程序视角下to空间的虚拟地址。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./3.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;3. Mutator 访问触发 Page Fault&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;当 mutator 尝试访问 to-space 中的对象（例如通过指针解引用），由于该页被设为 &lt;code&gt;PROT_NONE&lt;/code&gt;，会触发 &lt;strong&gt;Page Fault&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;操作系统将控制权交给 &lt;strong&gt;自定义的 Page Fault handler&lt;/strong&gt;（通过信号如 &lt;code&gt;SIGSEGV&lt;/code&gt; 或用户态缺页处理机制实现，或在内核模块中拦截）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. Page Fault Handler 的关键逻辑&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./4.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;// 假设 fault_addr 是触发缺页的地址
page = get_page(fault_addr);

// 步骤 1: GC 扫描该页中的所有对象（使用 GC 的映射地址）
scan_objects_in_page(page, gc_mapped_addr);

// 步骤 2: 将该页对 mutator 设为可读写
mprotect(mutator_mapped_addr_of_page, size, PROT_READ | PROT_WRITE);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;必须先完成 scan（确保页内所有对象已被正确复制、指针已更新），再开放访问权限&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;5. GC 的视角：始终可访问&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;GC 使用自己的一套映射（通过第二次 &lt;code&gt;mmap&lt;/code&gt;），该映射始终具有 &lt;code&gt;PROT_READ | PROT_WRITE&lt;/code&gt; 权限。&lt;/li&gt;
&lt;li&gt;因此，GC 可以随时扫描任何页，不受 &lt;code&gt;mprotect&lt;/code&gt; 限制。&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>GAMES101 作业3 </title><link>https://astro-pure.js.org/blog/games101_pa3/games101_pa3</link><guid isPermaLink="true">https://astro-pure.js.org/blog/games101_pa3/games101_pa3</guid><description>基于GAMES101 作业3</description><pubDate>Thu, 11 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一.作业要求&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;1.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;3.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2&gt;二、插值算法&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;void rst::rasterizer::rasterize_triangle(const Triangle&amp;#x26; t, const std::array&amp;#x3C;Eigen::Vector3f, 3&gt;&amp;#x26; view_pos) 
{
    // TODO: From your HW3, get the triangle rasterization code.
    // TODO: Inside your rasterization loop:
    //    * v[i].w() is the vertex view space depth value z.
    //    * Z is interpolated view space depth for the current pixel
    //    * zp is depth between zNear and zFar, used for z-buffer

    // float Z = 1.0 / (alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());
    // float zp = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();
    // zp *= Z;

    // TODO: Interpolate the attributes:
    // auto interpolated_color
    // auto interpolated_normal
    // auto interpolated_texcoords
    // auto interpolated_shadingcoords

    // Use: fragment_shader_payload payload( interpolated_color, interpolated_normal.normalized(), interpolated_texcoords, texture ? &amp;#x26;*texture : nullptr);
    // Use: payload.view_pos = interpolated_shadingcoords;
    // Use: Instead of passing the triangle&apos;s color directly to the frame buffer, pass the color to the shaders first to get the final color;
    // Use: auto pixel_color = fragment_shader(payload);
    
    int min_x = INT_MAX,min_y = INT_MAX,max_x = 0,max_y = 0;
    min_x = std::min({t.v[0].x(),t.v[1].x(),t.v[2].x()});
    max_x = std::max({t.v[0].x(),t.v[1].x(),t.v[2].x()});
    min_y = std::min({t.v[0].y(),t.v[1].y(),t.v[2].y()});
    max_y = std::max({t.v[0].y(),t.v[1].y(),t.v[2].y()});

    for(int x = min_x;x&amp;#x3C;=max_x;x++){
        for(int y = min_y;y&amp;#x3C;=max_y;y++){
            if(!insideTriangle(x,y,&amp;#x26;t.v[0])) continue;
            auto [alpha,beta,gamma] = computeBarycentric2D(x,y,&amp;#x26;t.v[0]);
            float Z = 1.0 / (alpha / t.v[0].w() + beta / t.v[1].w() + gamma / t.v[2].w());
            float zp = alpha * t.v[0].z() / t.v[0].w() + beta * t.v[1].z() / t.v[1].w() + gamma * t.v[2].z() / t.v[2].w();
            zp *= Z;

            if(depth_buf[get_index(x,y)] == std::numeric_limits&amp;#x3C;float&gt;::infinity() || 
                zp &gt; depth_buf[get_index(x,y)]){
                depth_buf[get_index(x,y)] = zp;
               
                auto interpolated_color = interpolate(alpha, beta, gamma, Eigen::Vector3f(t.color[0]/t.v[0].w()), Eigen::Vector3f(t.color[1] / t.v[1].w()) , Eigen::Vector3f(t.color[2] / t.v[2].w()) , 1/Z);

                auto interpolated_normal = interpolate(alpha,beta,gamma,Eigen::Vector3f(t.normal[0] / t.v[0].w()) ,Eigen::Vector3f(t.normal[1] / t.v[1].w()),Eigen::Vector3f(t.normal[2] / t.v[2].w()),1/Z);

                auto interpolated_texcoords = interpolate(alpha,beta,gamma,Eigen::Vector2f(t.tex_coords[0] / t.v[0].w()) ,Eigen::Vector2f(t.tex_coords[1] / t.v[1].w()) ,Eigen::Vector2f(t.tex_coords[2] / t.v[2].w()),1/Z);
                
                auto interpolated_shadingcolors = interpolate(alpha,beta,gamma,Eigen::Vector3f(view_pos[0]/t.v[0].w()),Eigen::Vector3f(view_pos[1]/t.v[1].w()),Eigen::Vector3f(view_pos[2]/t.v[2].w()),1/Z);

                fragment_shader_payload payload(interpolated_color, interpolated_normal.normalized(), interpolated_texcoords, texture ? &amp;#x26;*texture : nullptr);
                payload.view_pos = interpolated_shadingcolors;
                auto pixel_color = fragment_shader(payload);
                Eigen::Vector2i point(x,y);
                set_pixel(point,pixel_color);
            }
        }
    }    
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;与作业二类似，首先是求出包围盒的范围。然后枚举每一个像素，先判断是否在三角形内，然后再使用z-buffer算法，如果当前点更加靠前则更新depth_buf。接下来依次对颜色，法向量、纹理坐标、纹理底色进行插值，&lt;strong&gt;特别注意纹理底色要用视图空间当中的坐标进行插值！&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;三、Blinn-Phong模型&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;4.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;5.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;根据Blinn-Phong着色模型，光照由环境光、漫反射光和高光三部分组成。代入公式进行计算即可。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;Eigen::Vector3f phong_fragment_shader(const fragment_shader_payload&amp;#x26; payload)
{
    Eigen::Vector3f ka = Eigen::Vector3f(0.005, 0.005, 0.005);
    Eigen::Vector3f kd = payload.color;
    Eigen::Vector3f ks = Eigen::Vector3f(0.7937, 0.7937, 0.7937);

    auto l1 = light{{20, 20, 20}, {500, 500, 500}};
    auto l2 = light{{-20, 20, 0}, {500, 500, 500}};

    std::vector&amp;#x3C;light&gt; lights = {l1, l2};
    Eigen::Vector3f amb_light_intensity{10, 10, 10};
    Eigen::Vector3f eye_pos{0, 0, 10};

    float p = 150;

    Eigen::Vector3f color = payload.color;
    Eigen::Vector3f point = payload.view_pos;
    Eigen::Vector3f normal = payload.normal;

    Eigen::Vector3f result_color = {0, 0, 0};
    for (auto&amp;#x26; light : lights)
    {
        // TODO: For each light source in the code, calculate what the *ambient*, *diffuse*, and *specular* 
        // components are. Then, accumulate that result on the *result_color* object.
        auto v = eye_pos - point;
        auto l = light.position - point;
        auto h = (l+v).normalized();
        auto r2 = l.dot(l);

        auto La = ka.cwiseProduct(amb_light_intensity);
        auto Lb = kd.cwiseProduct(light.intensity/r2)*std::max(0.0f,normal.normalized().dot(l.normalized()));
        auto Ls = ks.cwiseProduct(light.intensity/r2)*std::pow(std::max(0.0f,normal.normalized().dot(h)),p);
        result_color += La+Lb+Ls;
    }

    return result_color * 255.f;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;特别注意点乘的两个向量要进行归一化操作！&lt;/p&gt;
&lt;h2&gt;四、纹理着色模型&lt;/h2&gt;
&lt;p&gt;与Bling-Phong模型相似，只需要在纹理图像上u，v取值得到点的颜色即可。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;Eigen::Vector3f texture_fragment_shader(const fragment_shader_payload&amp;#x26; payload)
{
    Eigen::Vector3f return_color = {0, 0, 0};
    if (payload.texture)
    {
        // TODO: Get the texture value at the texture coordinates of the current fragment
        return_color = payload.texture-&gt;getColor(payload.tex_coords[0],payload.tex_coords[1]);
    }
    Eigen::Vector3f texture_color;
    texture_color &amp;#x3C;&amp;#x3C; return_color.x(), return_color.y(), return_color.z();

    Eigen::Vector3f ka = Eigen::Vector3f(0.005, 0.005, 0.005);
    Eigen::Vector3f kd = texture_color / 255.f;
    Eigen::Vector3f ks = Eigen::Vector3f(0.7937, 0.7937, 0.7937);

    auto l1 = light{{20, 20, 20}, {500, 500, 500}};
    auto l2 = light{{-20, 20, 0}, {500, 500, 500}};

    std::vector&amp;#x3C;light&gt; lights = {l1, l2};
    Eigen::Vector3f amb_light_intensity{10, 10, 10};
    Eigen::Vector3f eye_pos{0, 0, 10};

    float p = 150;

    Eigen::Vector3f color = texture_color;
    Eigen::Vector3f point = payload.view_pos;
    Eigen::Vector3f normal = payload.normal;

    Eigen::Vector3f result_color = {0, 0, 0};

    for (auto&amp;#x26; light : lights)
    {
        // TODO: For each light source in the code, calculate what the *ambient*, *diffuse*, and *specular* 
        // components are. Then, accumulate that result on the *result_color* object.
        auto v = eye_pos - point;
        auto l = light.position - point;
        auto h = (l+v).normalized();
        auto r2 = l.dot(l);

        auto La = ka.cwiseProduct(amb_light_intensity);
        auto Lb = kd.cwiseProduct(light.intensity/r2)*std::max(0.0f,normal.normalized().dot(l.normalized()));
        auto Ls = ks.cwiseProduct(light.intensity/r2)*std::pow(std::max(0.0f,normal.normalized().dot(h)),p);
        result_color += La+Lb+Ls;
    }

    return result_color * 255.f;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;五、Bump着色模型&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;Eigen::Vector3f bump_fragment_shader(const fragment_shader_payload&amp;#x26; payload)
{
    
    Eigen::Vector3f ka = Eigen::Vector3f(0.005, 0.005, 0.005);
    Eigen::Vector3f kd = payload.color;
    Eigen::Vector3f ks = Eigen::Vector3f(0.7937, 0.7937, 0.7937);

    auto l1 = light{{20, 20, 20}, {500, 500, 500}};
    auto l2 = light{{-20, 20, 0}, {500, 500, 500}};

    std::vector&amp;#x3C;light&gt; lights = {l1, l2};
    Eigen::Vector3f amb_light_intensity{10, 10, 10};
    Eigen::Vector3f eye_pos{0, 0, 10};

    float p = 150;

    Eigen::Vector3f color = payload.color; 
    Eigen::Vector3f point = payload.view_pos;
    Eigen::Vector3f normal = payload.normal;


    float kh = 0.2, kn = 0.1;

    // TODO: Implement bump mapping here
    // Let n = normal = (x, y, z)
    // Vector t = (x*y/sqrt(x*x+z*z),sqrt(x*x+z*z),z*y/sqrt(x*x+z*z))
    // Vector b = n cross product t
    // Matrix TBN = [t b n]
    // dU = kh * kn * (h(u+1/w,v)-h(u,v))
    // dV = kh * kn * (h(u,v+1/h)-h(u,v))
    // Vector ln = (-dU, -dV, 1)
    // Normal n = normalize(TBN * ln)

    auto x = normal.x();
    auto y = normal.y();
    auto z = normal.z();

    auto u = payload.tex_coords[0];
    auto v = payload.tex_coords[1];

    auto w = payload.texture-&gt;width;
    auto h = payload.texture-&gt;height;

    Vector3f t(x * y / sqrt(x*x + z*z), sqrt(x*x + z*z), z * y / sqrt(x*x + z*z));
    Vector3f b = normal.normalized().cross(t.normalized());
    Matrix3f TBN;
    TBN &amp;#x3C;&amp;#x3C;  t.x(), b.x(), normal.x(),
            t.y(), b.y(), normal.y(),
            t.z(), b.z(), normal.z();

    auto h1 = (payload.texture-&gt;getColor(u+1.0f/w,v)).norm();
    auto h2 = (payload.texture-&gt;getColor(u,v+1.0f/h)).norm();
    auto h0 = (payload.texture-&gt;getColor(u,v)).norm();

    auto dU = kh * kn * (h1 - h0);
    auto dV = kh * kn * (h2 - h0);
    Vector3f ln(-dU,-dV,1.0f);
    normal = (TBN * ln).normalized();

    Eigen::Vector3f result_color = {0, 0, 0};
    result_color = normal;

    return result_color * 255.f;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;六、displacement贴图着色&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;Eigen::Vector3f displacement_fragment_shader(const fragment_shader_payload&amp;#x26; payload)
{
    
    Eigen::Vector3f ka = Eigen::Vector3f(0.005, 0.005, 0.005);
    Eigen::Vector3f kd = payload.color;
    Eigen::Vector3f ks = Eigen::Vector3f(0.7937, 0.7937, 0.7937);

    auto l1 = light{{20, 20, 20}, {500, 500, 500}};
    auto l2 = light{{-20, 20, 0}, {500, 500, 500}};

    std::vector&amp;#x3C;light&gt; lights = {l1, l2};
    Eigen::Vector3f amb_light_intensity{10, 10, 10};
    Eigen::Vector3f eye_pos{0, 0, 10};

    float p = 150;

    Eigen::Vector3f color = payload.color; 
    Eigen::Vector3f point = payload.view_pos;
    Eigen::Vector3f normal = payload.normal;

    float kh = 0.2, kn = 0.1;
    
    // TODO: Implement displacement mapping here
    // Let n = normal = (x, y, z)
    // Vector t = (x*y/sqrt(x*x+z*z),sqrt(x*x+z*z),z*y/sqrt(x*x+z*z))
    // Vector b = n cross product t
    // Matrix TBN = [t b n]
    // dU = kh * kn * (h(u+1/w,v)-h(u,v))
    // dV = kh * kn * (h(u,v+1/h)-h(u,v))
    // Vector ln = (-dU, -dV, 1)
    // Position p = p + kn * n * h(u,v)
    // Normal n = normalize(TBN * ln)

    auto x = normal.x();
    auto y = normal.y();
    auto z = normal.z();

    auto u = payload.tex_coords[0];
    auto v = payload.tex_coords[1];

    auto w = payload.texture-&gt;width;
    auto h = payload.texture-&gt;height;

    Vector3f t(x * y / sqrt(x*x + z*z), sqrt(x*x + z*z), z * y / sqrt(x*x + z*z));
    Vector3f b = normal.normalized().cross(t.normalized());
    Matrix3f TBN;
    TBN &amp;#x3C;&amp;#x3C;  t.x(), b.x(), normal.x(),
            t.y(), b.y(), normal.y(),
            t.z(), b.z(), normal.z();

    auto h1 = (payload.texture-&gt;getColor(u+1.0f/w,v)).norm();
    auto h2 = (payload.texture-&gt;getColor(u,v+1.0f/h)).norm();
    auto h0 = (payload.texture-&gt;getColor(u,v)).norm();

    auto dU = kh * kn * (h1 - h0);
    auto dV = kh * kn * (h2 - h0);

    Vector3f ln(-dU,-dV,1.0f);
    normal = (TBN * ln).normalized();
    point = point + kn * normal * h0;


    Eigen::Vector3f result_color = {0, 0, 0};

    for (auto&amp;#x26; light : lights)
    {
        // TODO: For each light source in the code, calculate what the *ambient*, *diffuse*, and *specular* 
        // components are. Then, accumulate that result on the *result_color* object.
        auto v = eye_pos - point;
        auto l = light.position - point;
        auto h = (l+v).normalized();
        auto r2 = l.dot(l);

        auto La = ka.cwiseProduct(amb_light_intensity);
        auto Lb = kd.cwiseProduct(light.intensity/r2)*std::max(0.0f,normal.normalized().dot(l.normalized()));
        auto Ls = ks.cwiseProduct(light.intensity/r2)*std::pow(std::max(0.0f,normal.normalized().dot(h)),p);
        result_color += La+Lb+Ls;
        
    }
    return result_color * 255.f;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>GAMES101 Lecture16 Ray Tracing04</title><link>https://astro-pure.js.org/blog/games101_lecture16/games101-lecture16-ray-tracing04</link><guid isPermaLink="true">https://astro-pure.js.org/blog/games101_lecture16/games101-lecture16-ray-tracing04</guid><description>基于GAMES101 Lecture 16的课程视频的总结</description><pubDate>Tue, 09 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Monte Carlo Integration（蒙特卡洛积分）&lt;/h2&gt;
&lt;p&gt;对于一个难以用解析法积分的函数，可以使用蒙特卡洛积分法。通过随机取函数值的平均来估计整个函数的积分值。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;1.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;其中p(x)为概率密度函数，n取的数量越多，结果方差越小，图像越平滑。&lt;/p&gt;
&lt;h2&gt;Path Tracing（路径追踪）&lt;/h2&gt;
&lt;p&gt;Whitted-Style光线追踪是错误的，但是渲染方程是正确的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;如何求解这个积分？使用蒙特卡洛积分！&lt;/p&gt;
&lt;h3&gt;第一步：直接光照的蒙特卡洛积分法&lt;/h3&gt;
&lt;p&gt;注意只计算半球，将整个函数当成f(x)!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;3.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;4.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;第二步：引入全局光照&lt;/h3&gt;
&lt;p&gt;如果击中的是另一个物体q，则应该递归调用shade(q,-wi)。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;5.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;第三步：解决光线爆炸问题（路径追踪诞生）&lt;/h3&gt;
&lt;p&gt;如果每个着色点都发送N条光线，经过几轮递归后，光线数量会呈指数级增长，计算成本无法承受。注意到只有当 &lt;code&gt;N=1&lt;/code&gt; 时，光线数量才不会爆炸。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;路径追踪（Path Tracing）：&lt;/strong&gt; 在每个着色点只随机采样&lt;strong&gt;一个&lt;/strong&gt;方向，发射&lt;strong&gt;一条&lt;/strong&gt;光线。通过在每个像素内追踪多条独立的“路径”（Path）并平均它们的辐射度来获得最终像素颜色。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;7.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;但是这样会无限递归弹射下去，光线无法停止，如何解决问题？&lt;/p&gt;
&lt;h3&gt;第四步：解决无限递归问题（俄罗斯轮盘赌）&lt;/h3&gt;
&lt;p&gt;俄罗斯轮盘赌（Russian Roulette, RR）：&lt;/p&gt;
&lt;p&gt;设定一个存活概率 P(0 &amp;#x3C; P &amp;#x3C; 1)。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;以概率 &lt;code&gt;P&lt;/code&gt; 继续追踪光线，并将结果除以 &lt;code&gt;P&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;以概率 &lt;code&gt;1-P&lt;/code&gt; 停止追踪，返回0。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;最终的数学期望不变：&lt;/strong&gt; &lt;code&gt;E = P * (Lo / P) + (1-P) * 0 = Lo&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;8.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;在半球上均匀采样时，击中光源的概率可能很低，大部分计算被“浪费”在没有击中光源的光线上,如何加快运算？&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;第五步：提高效率（光源采样）&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;光源采样&lt;/strong&gt;：直接在光源表面进行采样，确保每条采样光线都“有用”。&lt;/p&gt;
&lt;p&gt;需要将渲染方程从对立体角 &lt;code&gt;dω&lt;/code&gt; 的积分，转换为对光源面积 &lt;code&gt;dA&lt;/code&gt; 的积分。转换关系为：&lt;code&gt;dω = (cosθ&apos; / ||x&apos;-x||²) dA&lt;/code&gt;，其中 &lt;code&gt;θ&apos;&lt;/code&gt; 是光源表面法线与光线的夹角。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;10.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;最终算法&lt;/h3&gt;
&lt;p&gt;在 shade函数中，将光照分为两部分计算：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;直接光照：&lt;/strong&gt; 直接在光源上采样，计算贡献（无需RR）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;间接光照：&lt;/strong&gt; 在半球上采样，递归调用 &lt;code&gt;shade&lt;/code&gt;（需要RR）。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;11.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;阴影判断：&lt;/strong&gt; 对于光源采样得到的点，需要发射一条“阴影光线”来判断它是否被其他物体遮挡。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;12.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>GAMES101 Lecture17 Materials and Appearances</title><link>https://astro-pure.js.org/blog/games101_lecture17/games101-lecture17-materials-and-appearances</link><guid isPermaLink="true">https://astro-pure.js.org/blog/games101_lecture17/games101-lecture17-materials-and-appearances</guid><description>基于GAMES101 Lecture 17的课程视频的总结</description><pubDate>Tue, 09 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Material==BRDF&lt;/h2&gt;
&lt;p&gt;BRDF决定了光如何被反射，即反射光的形式。&lt;/p&gt;
&lt;h3&gt;漫反射材质&lt;/h3&gt;
&lt;p&gt;光线会被反射到各个方向上去。&lt;/p&gt;
&lt;p&gt;假设光线均匀射入，即$L_i$是一个常数，推导出射光如下图所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;1.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;此时brdf的值为1/pi,albedo的取值为0~1。&lt;/p&gt;
&lt;h3&gt;Glossy material&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;2.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;3.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Ideal reflective / refractive material (BSDF)&lt;/h3&gt;
&lt;p&gt;BSDF(散射)=BRDF（反射）+BTDF（折射）&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;4.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Perfect Specular Reflection&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;5.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;从上往下看，入射和出射的方位角相反。&lt;/p&gt;
&lt;h3&gt;斯涅尔法则（即折射定律）&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;6.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;7.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Fresnel Reflection / Term(菲涅尔项)&lt;/h3&gt;
&lt;p&gt;如下图所示，以不同的角度观察有不同程度的反射效果。反射率（reflectance）取决于入射角（以及光的偏振状态）；在这个例子中，反射率随着入射角趋近于掠射角（grazing angle）而增加。当入射角度为0时，反射率最小，当入射角度接近90度时，反射率接近于1。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;8.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;菲涅尔公式如下所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;9.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;反射率R&lt;strong&gt;对s 偏振光&lt;/strong&gt;（垂直偏振）和&lt;strong&gt;p 偏振光&lt;/strong&gt;（平行偏振）有不同的响应，方便计算取下方的近似公式计算！&lt;/p&gt;
&lt;h2&gt;Microfacet Material（微表面材质）&lt;/h2&gt;
&lt;p&gt;从远处看一个表面，看到是材质和外观，而从近处看，能看到表面上具体的几何。每一个微表面都有它自己的法向量。&lt;/p&gt;
&lt;p&gt;当法向量的分布比较集中时，就是glossy，而比较分散时，就是diffuse。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;10.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Microfacet BRDF&lt;/h3&gt;
&lt;p&gt;每一个微表面我们都把它当成镜面。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;11.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;第一项是菲涅尔项，从不同的角度入射有不同的反射率；第二项是shadowing-masking项，表示表面其他部分对它的几何遮挡程度；第三项是法向量的分布，&lt;strong&gt;D(h)&lt;/strong&gt; 表示：在某个方向 &lt;strong&gt;h&lt;/strong&gt; 上，有多少微表面的法线指向该方向，只有法线与半角向量 &lt;strong&gt;h&lt;/strong&gt; 对齐的微表面才会将入射光反射到出射方向&lt;/p&gt;
&lt;h3&gt;Isotropic / Anisotropic Materials (BRDFs)（各向同性/各向异性）&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;13.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;12.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;各向同性：brdf与绝对方位角关系很小，只与相对方位角有关，从4维降到了3维。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;各向异性：brdf与绝对方位角也有关系，需要用四个量来描述。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;BRDF的性质&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;15.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;14.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;非负性；线性；光路可逆性；能量守恒&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>GAMES101 Lecture15 Ray Tracing03</title><link>https://astro-pure.js.org/blog/games101_lecture15/games101-lecture15-ray-tracing03light-transport--global-illumination</link><guid isPermaLink="true">https://astro-pure.js.org/blog/games101_lecture15/games101-lecture15-ray-tracing03light-transport--global-illumination</guid><description>基于GAMES101 Lecture 15的课程视频的总结</description><pubDate>Mon, 08 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;辐射度量学（Radiometry）&lt;/h2&gt;
&lt;h3&gt;概念回顾&lt;/h3&gt;
&lt;p&gt;辐射能量；辐射通量；辐射强度；立体角&lt;img src=&quot;1.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;微分立体角&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;2.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Irradiance&lt;/h3&gt;
&lt;p&gt;Irradiance是指单位面积上入射到表面点的功率。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;3.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Lambert’s Cosine Law&lt;/h3&gt;
&lt;p&gt;通常情况，单位面积上的能量正比于余弦值。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;4.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Radiance&lt;/h3&gt;
&lt;p&gt;Radiance是描述环境中光分布的基本场量。渲染就是在计算radiance。&lt;/p&gt;
&lt;p&gt;定义：单位立体角内、单位投影面积上表面所发射、反射、透射或接收的功率。&lt;/p&gt;
&lt;p&gt;可以理解为单位立体角上的irradiance，也可以理解为单位投影面积上的intensity。&lt;/p&gt;
&lt;p&gt;Irradiance是单位面积上所有方向的光，而radiance是某个立体角上的光。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;5.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h4&gt;Incident Radiance 入射&lt;/h4&gt;
&lt;p&gt;是指单位立体角上到达表面上的irradiance。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;6.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h4&gt;Exiting Radiance 出射&lt;/h4&gt;
&lt;p&gt;是指单位投影面积上的intensity，与上面incident radiance的形式是等价的，只是两种理解radiance的视角。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;7.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Irradiance vs. Radiance&lt;/h3&gt;
&lt;p&gt;Irradiance:面积dA上接收到的所有能量；&lt;/p&gt;
&lt;p&gt;Radiance:面积dA从方向dω来接收到的能量。&lt;/p&gt;
&lt;p&gt;将所有方向的Radiance累加起来就是Irradiance。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;8.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Bidirectional Reflectance Distribution Function (BRDF) 双向反射分布函数&lt;/h2&gt;
&lt;p&gt;微分式将入射和出射radiance联系在一起。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;9.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;10.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;反射方程&lt;/h3&gt;
&lt;p&gt;某个出射角的出射radiance等于将所有入射角的radiance累加起来的值。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;11.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;但是注意到解出反射后的radiance需要入射radiance，而同时入射radiance也有可能是另外一个物体的反射的radiance，形成递归方程。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;加上光源直接发射项可以得到完整的渲染方程：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;12.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;注意：一般只考虑半球，而不考虑下半球贡献的光。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;13.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;光线弹射一次称为直接光照，两次及以上为间接光照，全局光照为&lt;strong&gt;直接光照+间接光照&lt;/strong&gt;。&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>GAMES101 Lecture13 Ray Tracing02</title><link>https://astro-pure.js.org/blog/games101_lecture14/games101-lecture13-ray-tracing04</link><guid isPermaLink="true">https://astro-pure.js.org/blog/games101_lecture14/games101-lecture13-ray-tracing04</guid><description>基于GAMES101 Lecture 14的课程视频的总结</description><pubDate>Sun, 31 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Uniform Spatial Partitions (Grids)（统一空间网格划分）&lt;/h2&gt;
&lt;h3&gt;预处理-建立加速网格&lt;/h3&gt;
&lt;p&gt;1.找到包围盒&lt;/p&gt;
&lt;p&gt;2.建立网格&lt;/p&gt;
&lt;p&gt;3.存储每个物体在哪些重叠网格当中&lt;/p&gt;
&lt;p&gt;4.对于光线，遍历每个格子中与之相交的所有物体&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;1.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;关于网格分辨率&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;格子太少：无法加速物体相交判断&lt;/li&gt;
&lt;li&gt;格子太多：需要判断许多无关格子，减慢速度&lt;/li&gt;
&lt;li&gt;最佳格子数量：C*物体数量，其中C=27 in 3D&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Spatial Partitiions（空间划分）&lt;/h2&gt;
&lt;h3&gt;三种划分方法&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;2.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2&gt;KD-Tree划分过程&lt;/h2&gt;
&lt;p&gt;按照坐标轴方向进行划分，二叉树结构，叶结点存储了包含的物体，内部节点不存储物体。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;3.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;遍历过程：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;4.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Object Partitions &amp;#x26; Bounding Volume Hierarchy (BVH)&lt;/h2&gt;
&lt;p&gt;前面的KD-Tree是对空间进行划分，而BVH是对物体进行划分。&lt;/p&gt;
&lt;p&gt;将一系列物体划分成两类，分别求出他们的包围盒范围，以此类推，递归求解，存储物体在每个叶结点中，并且的每个物体仅存储在一个叶结点中（与KD-Tree进行对比）。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;5.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;如何选择划分物体的依据呢？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;选择一个维度去进行划分操作&lt;/li&gt;
&lt;li&gt;依据1：每次都选择最长的维度进行划分&lt;/li&gt;
&lt;li&gt;依据2：以中间物体作为划分依据&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;BVHs的数据结构&lt;/h3&gt;
&lt;p&gt;内部节点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;包围盒&lt;/li&gt;
&lt;li&gt;子节点&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;叶子节点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;包围盒&lt;/li&gt;
&lt;li&gt;物体列表&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;BVH遍历伪代码如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;6.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2&gt;空间划分和物体划分的对比&lt;/h2&gt;
&lt;p&gt;主要区别在于空间是否重叠以及一个物体是否唯一包含在一个节点内。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;7.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Radiometry(辐射度量学)&lt;/h2&gt;
&lt;p&gt;Radiant Flux（辐射通量）：&lt;img src=&quot;8.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;几种度量光的概念：&lt;img src=&quot;9.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;光照强度：整个球体的立体角为4pi&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;10.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>GAMES101 Lecture13 Ray Tracing01</title><link>https://astro-pure.js.org/blog/games101_lecture13/games101-lecture13-ray-tracing01</link><guid isPermaLink="true">https://astro-pure.js.org/blog/games101_lecture13/games101-lecture13-ray-tracing01</guid><description>基于GAMES101 Lecture 13的课程视频的总结</description><pubDate>Fri, 22 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Basic Ray-Tracing Algorithm(光线追踪基本算法)&lt;/h2&gt;
&lt;h3&gt;关于光线的三个假定&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;光沿直线传播&lt;/li&gt;
&lt;li&gt;两条光线相交后不会发生碰撞，仍然保持各自的运动方向&lt;/li&gt;
&lt;li&gt;光线从光源入射到眼睛里&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;物理基础&lt;/h3&gt;
&lt;p&gt;根据光路可逆，我们可以将从光源射入眼睛的光线路径改为从眼睛发射出光线，到达环境中，实现光线追踪。&lt;/p&gt;
&lt;h3&gt;Recursive (Whitted-Style) Ray Tracing算法流程&lt;/h3&gt;
&lt;p&gt;1.&lt;strong&gt;生成主光线 (Primary Rays)&lt;/strong&gt;：为图像中的每个像素生成一条从摄像机（眼点）出发，穿过该像素的“视线”（Eye Ray）。&lt;/p&gt;
&lt;p&gt;2.&lt;strong&gt;求交测试 (Intersection)&lt;/strong&gt;：计算这条光线与场景中所有物体的交点，并找出&lt;strong&gt;距离最近&lt;/strong&gt;的交点。&lt;/p&gt;
&lt;p&gt;3.&lt;strong&gt;局部着色 (Local Shading)&lt;/strong&gt;：在最近的交点处，使用局部光照模型（如Blinn-Phong）计算该点的颜色。这需要知道该点是否被光源照亮。&lt;/p&gt;
&lt;p&gt;4.&lt;strong&gt;阴影测试 (Shadow Rays)&lt;/strong&gt;：为了判断交点是否在阴影中，从交点向光源发射一条“阴影光线”（Shadow Ray）。如果这条光线在到达光源前与任何物体相交，则该点处于阴影中。&lt;/p&gt;
&lt;p&gt;5.递归追踪 (Recursive Tracing)：除了主光线和阴影光线，还会生成二次光线 (Secondary Rays)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;反射光线 (Reflected Ray)&lt;/strong&gt;：用于模拟镜面反射。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;折射光线 (Refracted Ray)&lt;/strong&gt;：用于模拟透明物体的透射（如玻璃）。 这些二次光线会以同样的方式（求交、着色、阴影、递归）继续追踪下去，直到达到预设的递归深度或光线能量过低。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;1.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Ray-Surface Intersection(光线-物体求交)&lt;/h2&gt;
&lt;p&gt;光线与物体求交点是光线追踪性能的关键瓶颈。&lt;/p&gt;
&lt;h3&gt;光线定义&lt;/h3&gt;
&lt;p&gt;光线就是一条射线，可以用向量来描述方向和长度。
$$
\mathbf{r}(t) = \mathbf{o} + t\mathbf{d} \quad 0 \leq t &amp;#x3C; \infty
$$&lt;/p&gt;
&lt;h3&gt;光线与球体求交点&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;2.png&quot; alt=&quot;2&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;3.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;光线与隐式平面相交&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;4.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;与三角形网格相交&lt;/h3&gt;
&lt;p&gt;两个步骤：&lt;/p&gt;
&lt;h4&gt;1.先判断与三角形所在平面交点&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;5.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h4&gt;2.判断交点是否在三角形内&lt;/h4&gt;
&lt;p&gt;利用三个点中的两个顶点计算向量点乘正负性即可判断是否在三角形内&lt;/p&gt;
&lt;h4&gt;另一种快速的计算交点方法（Möller Trumbore Algorithm）&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;6.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;$1-b_1-b_2+b_1+b_2=1$即系数为1，表示在一个平面内,求解x,y,z三个方向上的方程组，用线性代数知识解决。&lt;/p&gt;
&lt;h3&gt;包围盒（Bounding Volumes）&lt;/h3&gt;
&lt;p&gt;这是一种加速计算交点的方法，用一个长方体来全部包括物体，我们通常使用的是轴对齐包围盒（Axis-Aligned Bounding Box）。如果光线与该包围盒无交点，则肯定与该物体没有交点，有交点的话再根据上面几种方法来计算交点。&lt;/p&gt;
&lt;p&gt;每一次只计算一对面，共三个面，求得到达面包围盒的时间$t_{min}$和离开包围盒的时间$t_{max}$ 。最终取得光线进入包围盒的时间为$t_{enter}=max{t_{min}}$，离开包围盒的时间为$t_{exit}=max{t_{max}}$&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;7.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;几种时间情况：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;8.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;当且仅当进入时间小于离开时间并且离开时间≥0！&lt;/p&gt;
&lt;h2&gt;性能开销&lt;/h2&gt;
&lt;p&gt;朴素的光线追踪算法（每个像素的每条光线都与场景中所有物体求交）计算量巨大，复杂度为 &lt;code&gt;O(像素数 × 物体数 × 反射次数)&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;而使用&lt;strong&gt;包围体 (Bounding Volumes)&lt;/strong&gt;，如AABB。先与包围体求交，只有当光线与包围体相交时，才需要去检查其内部的复杂物体，从而大幅减少不必要的求交计算。&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>GAMES101 Lecture10 Geometry01</title><link>https://astro-pure.js.org/blog/games101_lecture10/games101-lecture10-geometry01</link><guid isPermaLink="true">https://astro-pure.js.org/blog/games101_lecture10/games101-lecture10-geometry01</guid><description>基于GAMES101 Lecture 10的课程视频和虎书的总结</description><pubDate>Sat, 21 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Applications of Textures&lt;/h2&gt;
&lt;h3&gt;Environment Map&lt;/h3&gt;
&lt;p&gt;我们可以将环境光做成贴图贴在物体的表面，实现物体反射环境光的效果。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;1.png&quot; alt=&quot;image-20250621152747404&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Spherical Environment Map（球形环境贴图）&lt;/h3&gt;
&lt;p&gt;将环境光贴图映射到一个球上，问题在于靠近极点的上下两侧会发生扭曲现象。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2.png&quot; alt=&quot;image-20250621153115550&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Cube Map&lt;/h3&gt;
&lt;p&gt;将环境光映射到一个立方体六个面上，解决了Spherical Environment Map扭曲的问题。当我们要查询来自某个方向的光线时，需要先求出信息记录在立方体哪个表面上。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;3.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;4.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;纹理不仅可以用来表示颜色，还可以表示表面凹凸程度。贴图存储相对高度，欺骗人眼认为表面有凹凸变化，几何形体并没有发生实际的形状变化！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;5.png&quot; alt=&quot;&quot;&gt;&lt;img src=&quot;6.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Bump Mapping&lt;/h3&gt;
&lt;p&gt;通过改变相对高度来改变法线方向，而不会增加更多的几何形体。&lt;/p&gt;
&lt;p&gt;如何找到法线方向？借助切线方向即可找到！&lt;/p&gt;
&lt;p&gt;二维情况：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;7.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;三维情况：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;8.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Displacement mapping(位移贴图)&lt;/h3&gt;
&lt;p&gt;凹凸贴图或者法线贴图在边缘无法表现凹凸，且无法表示物体局部凸起对于其他部分的阴影。我们可以用到Displacement mapping，它真正地移动了顶点，而不仅仅只是计算出了法线。&lt;/p&gt;
&lt;p&gt;位移贴图要求模型本身的三角形足够细致，便于凹凸纹理变化三角形的顶点位置！&lt;/p&gt;
&lt;p&gt;如果三角形较少的粗糙模型需要使用位移贴图，就要使用到dynamic tessellation（动态曲面细分），在应用位移贴图的过程中检测是否需要将三角形细分。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;9.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;3D Procedural Noise + Solid Modeling&lt;/h3&gt;
&lt;p&gt;定义空间中的噪声函数，可以计算出空间中任意一个点的噪声值，通过一系列的操作将二维纹理映射到空间中。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;10.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Provide Precomputed Shading&lt;/h3&gt;
&lt;p&gt;如下图，纹理还可以预先计算环境光遮蔽，加快渲染速度。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;11.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Geometry&lt;/h2&gt;
&lt;h3&gt;Two ways to represent geometry&lt;/h3&gt;
&lt;h4&gt;Implicit(隐式的)&lt;/h4&gt;
&lt;p&gt;不显示说明每个点具体在哪里，只告诉这些点满足的关系，所有满足f(x,y,z)=0的点构成的几何体。&lt;/p&gt;
&lt;p&gt;例如球的隐式表达：$x^2+y^2+z^2=1$&lt;/p&gt;
&lt;p&gt;采样所有的点是困难的，但是可以简单地检测点是在几何内部还是外部&lt;/p&gt;
&lt;h4&gt;Explicit（显式的）&lt;/h4&gt;
&lt;p&gt;将关于x,y,z的函数转为u,v 的函数-&gt;参数方程&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;12.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;采样所有的点是容易的，但是检测一个点是在内部还是外部是困难的。&lt;/p&gt;
&lt;h3&gt;More Implicit Representations in Computer Graphics&lt;/h3&gt;
&lt;h4&gt;Constructive Solid Geometry(CSG)&lt;/h4&gt;
&lt;p&gt;在计算机图形学中隐式表示几何体还可以使用交、并、补布尔运算。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;13.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h4&gt;Distance Functions&lt;/h4&gt;
&lt;p&gt;求出一个点到物体表面的最短距离 ，距离为正在物体外，为负在物体内。A、B是两个不同的图，我们可以用距离函数来解决这个问题，SDF(A)， SDF(B) 物体边界处的值接近0，blend 两个距离函数相当于是在blend两个物体的边界，最后再把blend(SDF(A),SDF(B))恢复成物体，为0表示边界。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;14.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Level Set Methods（水平集）&lt;/h3&gt;
&lt;p&gt;如果距离函数的表达式不好写，可以使用水平集来表达距离函数，存储在一个矩阵当中，边界就是值为0的点的集合（可以使用插值方法计算出任何一个点的值）。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;15.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>CSAPP 第五章程序优化总结</title><link>https://astro-pure.js.org/blog/%E6%96%B0%E5%BB%BA%E6%96%87%E4%BB%B6%E5%A4%B9/csapp%E7%AC%AC%E4%BA%94%E7%AB%A0%E7%A8%8B%E5%BA%8F%E4%BC%98%E5%8C%96</link><guid isPermaLink="true">https://astro-pure.js.org/blog/%E6%96%B0%E5%BB%BA%E6%96%87%E4%BB%B6%E5%A4%B9/csapp%E7%AC%AC%E4%BA%94%E7%AB%A0%E7%A8%8B%E5%BA%8F%E4%BC%98%E5%8C%96</guid><description>基于Whu计院的CSAPP第五章程序优化总结</description><pubDate>Mon, 19 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;常用优化方法&lt;/h2&gt;
&lt;h3&gt;消除编译时常量&lt;/h3&gt;
&lt;p&gt;如果表达式中的全部或部分成分是编译时常量，则可以在编译时完成一部分计算，从而减少运行时的计算开销。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;a = 10;
b = 20;
c = a;
d = c+b;

if (d &gt; 20)
f();
else
g();
return;

//可以化为：
f();
return;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;减少重复计算&lt;/h3&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>CSAPP 第七章链接总结</title><link>https://astro-pure.js.org/blog/csapp%E7%AC%AC%E4%B8%83%E7%AB%A0linking/csapp%E7%AC%AC%E4%B8%83%E7%AB%A0linking</link><guid isPermaLink="true">https://astro-pure.js.org/blog/csapp%E7%AC%AC%E4%B8%83%E7%AB%A0linking/csapp%E7%AC%AC%E4%B8%83%E7%AB%A0linking</guid><description>基于Whu计院的CSAPP第七章链接总结</description><pubDate>Sun, 18 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;编译器驱动程序&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;main.c -&gt; main.i（ASCII码中间文件）-&gt; main.s （ASCII码汇编语言文件）-&gt;main.o（可重定位目标文件）-&gt; prog （运行链接器链接其他文件生成可执行目标文件)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;调用loader（加载器）将prog中代码和数据复制到内存，控制转移到程序开头开始运行。&lt;/p&gt;
&lt;h2&gt;静态链接&lt;/h2&gt;
&lt;p&gt;两个主要任务：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;符号解析&lt;/strong&gt;：将符号引用和符号定义关联起来（函数，全局变量，静态变量），存储在目标文件的&lt;strong&gt;符号表&lt;/strong&gt;中。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;重定位&lt;/strong&gt;：将符号定义和内存地址关联起来，让符号的引用指向对应的内存位置。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;ELF文件（可重定位目标文件）&lt;/h2&gt;
&lt;p&gt;支持三种目标文件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;可重定位的目标文件 (.o)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;可执行的目标文件 (a.out)：可直接拷贝到内存并执行&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;共享的目标文件 (.so)：可以被加载到内存进行动态链接（在加载时或者运行时完成链接），win中称为DLL&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;ELF目标文件格式&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./1.png&quot; alt=&quot;1&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ELF header(ELF头):Word size 字长的大小, 字节顺序, 文件类型 (.o, exec, .so), 机器类型, etc.&lt;/li&gt;
&lt;li&gt;.text section：已编译程序的机器代码&lt;/li&gt;
&lt;li&gt;.rodata section：只读数据，如常量字符串和switch跳转表&lt;/li&gt;
&lt;li&gt;.data section：&lt;strong&gt;初始化的全局或者静态C变量&lt;/strong&gt;（局部变量在栈上）&lt;/li&gt;
&lt;li&gt;.bss section：&lt;strong&gt;未初始化的全局和静态变量，以及所有被初始化为0的全局或静态变量&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;.symtab section：符号表，存放定义和引用的函数和全局变量的信息，不包含局部变量条目&lt;/li&gt;
&lt;li&gt;.rel.text：text节对应的重定位信息&lt;/li&gt;
&lt;li&gt;.rel.data：data节对应的重定位信息&lt;/li&gt;
&lt;li&gt;.debug：调试符号表&lt;/li&gt;
&lt;li&gt;Section header table：节头表，存储了每个节的偏移和大小&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;符号和符号表&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;全局符号&lt;/strong&gt;：当前模块中&lt;strong&gt;定义&lt;/strong&gt;的全局符号，可以被其他模块引用&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;外部符号&lt;/strong&gt;：当前模块中&lt;strong&gt;引用&lt;/strong&gt;的全局符号，但是在其他模块中定义&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;局部符号&lt;/strong&gt;：当前模块中定义，且只被当前模块引用（用static修饰的C函数和全局变量），&lt;strong&gt;局部链接器符号!=程序的局部符号&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;符号解析&lt;/h2&gt;
&lt;p&gt;编译器为不是在当前模块中定义的符号生成一个&lt;strong&gt;链接器符号表条目&lt;/strong&gt;，并交由链接器处理。&lt;/p&gt;
&lt;h3&gt;解决重复的符号定义&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;强符号&lt;/strong&gt;：函数定义，以及初始化了的全局变量&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;弱符号&lt;/strong&gt;：未初始化的全局变量&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;链接器符号规则：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不允许存在多个强符号&lt;/li&gt;
&lt;li&gt;如果有1个强符号定义，和多个弱符号定义：将所有符号引用，解析到强符号定义&lt;/li&gt;
&lt;li&gt;如果有多个弱符号，任意选择一个&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;特别要注意下面这种情况：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;//main.c
#include &amp;#x3C;stdio.h&gt;
int d=100;
int x=200;
void p1(void);
int main() 
{  
     p1();
     printf(“d=%d,x=%d\n”,d,x);
     return 0;
}
//p1.c
double d;
void p1(){
    d = 1.0;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个示例中d一次强定义，一次弱定义，所有对d的引用都是指向同一个int类型的d的内存地址&lt;/p&gt;
&lt;p&gt;而在p1.c中将d视作double类型，内存占8个字节，int类型占4个字节，赋值将会覆盖高地址变量x的内容。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./3.png&quot; alt=&quot;3&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./4.png&quot; alt=&quot;4&quot;&gt;&lt;/p&gt;
&lt;p&gt;全局变量的使用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;如果可以，请使用 &lt;strong&gt;static&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果你定义了一个全局变量，请初始化&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果你引用了外部的全局变量，请使用 &lt;strong&gt;extern&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;重定位&lt;/h2&gt;
&lt;p&gt;重定义由两步完成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;重定位节和符号定义：多文件合并、分配地址&lt;/li&gt;
&lt;li&gt;重定位节中的符号引用：指向正确的运行位置&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;重定位条目&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./5.png&quot; alt=&quot;5&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;offset：需要重定位位置的偏移量&lt;/li&gt;
&lt;li&gt;addend：与下一条指令（PC值）的距离&lt;/li&gt;
&lt;li&gt;type：分为绝对寻址和PC相对寻址&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;1.PC相对寻址&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;引用符号的位置（refAddr） = ADDR(s) + r.offset    节的位置+重定位位置偏移量&lt;/p&gt;
&lt;p&gt;当前PC = refAddr - r.addend     特别注意是减号&lt;/p&gt;
&lt;p&gt;重定位值 = r.symbol.addr - 当前PC&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./6.png&quot; alt=&quot;6&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2.绝对寻址&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;重定位值 = ADDR(r.symbol) + r.addend&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;可执行目标文件&lt;/h2&gt;
&lt;h3&gt;可执行目标文件格式&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./2.png&quot; alt=&quot;2&quot;&gt;&lt;/p&gt;
&lt;p&gt;与可重定位目标文件的不同点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ELF头中的e_entry给出了&lt;strong&gt;执行程序的第一条指令地址&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;多了.init节，用于定义_init函数&lt;/li&gt;
&lt;li&gt;少了两个.rel节，执行文件无需重定位&lt;/li&gt;
&lt;li&gt;多了Program header table（&lt;strong&gt;用于描述如何将节映射到存储空间&lt;/strong&gt;），&lt;strong&gt;替换了可重定位文件中的节头表，以便加载到内存&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;加载和运行&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;加载器(使用系统调用execve)将代码和数据段从磁盘文件拷贝到内存&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;running the code by jumping to its entry point（定义在ELF头中）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;跳转到文件的入口点（entry point)，开始执行&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;_start (from crt1.o, runtime)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;_libc_start_main (from libc.so, libc)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;main&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;链接共享库&lt;/h2&gt;
&lt;h3&gt;静态链接&lt;/h3&gt;
&lt;p&gt;.a为后缀的archive包（将多个相关的可重定位目标文件串接成一个带索引的单一文件，称为archive包）。&lt;/p&gt;
&lt;p&gt;链接过程：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;链接器维护一个未解引用的列表，按照命令行参数顺序依次扫描是否能够找到定义&lt;/li&gt;
&lt;li&gt;解析结果受&lt;strong&gt;参数顺序&lt;/strong&gt;的影响!最佳实践: 参数顺序中，将.a文件放在.o文件的后面。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;链接顺序：&lt;/p&gt;
&lt;p&gt;按照拓扑排序顺序最佳。&lt;/p&gt;
&lt;h3&gt;动态链接&lt;/h3&gt;
&lt;p&gt;将这些常用函数做成一种二进制文件，其中的代码和数据可以在&lt;strong&gt;加载时&lt;/strong&gt;或者&lt;strong&gt;运行时&lt;/strong&gt;动态地链接到应用程序。&lt;/p&gt;
&lt;p&gt;Linux(.so文件)、Windows(.dll文件)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;加载时链接：在可执行文件第一次被加载时完成动态链接&lt;/li&gt;
&lt;li&gt;加载时链接：在可执行文件第一次被加载时完成动态链接（软件分发，&lt;strong&gt;库插桩&lt;/strong&gt;）&lt;/li&gt;
&lt;li&gt;共享库可以被多个进程共享&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;库打桩机制&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;编译时打桩：将对 malloc/free 的调用宏扩展为对 mymalloc/myfree 的调用&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;链接时打桩：用--wrap f标志进行链接时打桩，把f符号解析为&lt;code&gt;_ _wrap_f&lt;/code&gt;,符号&lt;code&gt;_ _real_f&lt;/code&gt;解析为f&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;加载/运行时打桩：基于动态链接器的LD_PRELOAD环境变量，设置为共享库路径名列表，解析引用时先搜索LD_PRELOAD库&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>GAMES101 Lecture09 Shading 3</title><link>https://astro-pure.js.org/blog/games101_lecture9/games101_09</link><guid isPermaLink="true">https://astro-pure.js.org/blog/games101_lecture9/games101_09</guid><description>Lecture_09_Shading 3 (Texture Mapping cont.)</description><pubDate>Thu, 15 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Barycentric coordinates（重心坐标）&lt;/h2&gt;
&lt;p&gt;为什么插值？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;指定顶点上的值&lt;/li&gt;
&lt;li&gt;获得三角形内平滑变化的值&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可以插值什么内容？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Texture coordinates（纹理坐标）, colors（颜色）, normal vectors（法线），...&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;重心坐标公式&lt;/h3&gt;
&lt;p&gt;$$(x,y)=αA+βB+γC$$&lt;/p&gt;
&lt;p&gt;$α+β+γ=1且α&gt;=0,β&gt;=0,γ&gt;=0$&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./1.png&quot; alt=&quot;1&quot;&gt;&lt;/p&gt;
&lt;p&gt;由$(x,y)$可求得$α，β，γ$：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./2.png&quot; alt=&quot;2&quot;&gt;&lt;/p&gt;
&lt;p&gt;注意：&lt;strong&gt;重心坐标在投影下是会变化的&lt;/strong&gt;。&lt;/p&gt;
&lt;h2&gt;Applying Textures（应用纹理）&lt;/h2&gt;
&lt;p&gt;我们要将纹理应用到物体上。(x,y)是像素坐标，(u,v)是纹理坐标。&lt;/p&gt;
&lt;p&gt;纹理应用步骤：&lt;/p&gt;
&lt;p&gt;对于每一个光栅化的屏幕像素：
(u,v) = 计算(x,y)处对应的纹理坐标  &lt;em&gt;(x,y)是使用的重心坐标&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;​	texcolor(纹理颜色) = texture.sample(u,v);  &lt;em&gt;纹理上取样纹理坐标(u,v)处的颜色&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;​	将样本颜色设置为texcolor; &lt;em&gt;通常会使用Blinn-Phong reflectance model&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Texture Magnification 纹理放大&lt;/h2&gt;
&lt;h3&gt;Easy Case（纹理太小）&lt;/h3&gt;
&lt;p&gt;这意味着多个个像素需要对应同一个&lt;strong&gt;texel&lt;/strong&gt;（纹理元素，纹素，即在纹理上的一个像素）&lt;/p&gt;
&lt;p&gt;我们可以使用&lt;strong&gt;Bilinear Interpolation&lt;/strong&gt;（双线性插值，考虑周围2x2邻域）和&lt;strong&gt;Bicubic Interpolation&lt;/strong&gt;（双三次插值，考虑周围4x4邻域）来解决这个问题。&lt;/p&gt;
&lt;p&gt;下面介绍的是Bilinear Interpolation（双线性插值法）：&lt;/p&gt;
&lt;p&gt;如何取样纹理上(u,v)处的值f(x,y)?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./3.png&quot; alt=&quot;3&quot;&gt;&lt;/p&gt;
&lt;p&gt;先进行水平插值，求出$u_1$和$u_0$，再进行垂直插值求出$f(x,y)$&lt;/p&gt;
&lt;p&gt;处理效果：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./4.png&quot; alt=&quot;4&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Hard case(纹理太大)&lt;/h3&gt;
&lt;p&gt;如果我们还是使用点插值，将会出现&lt;strong&gt;多个纹理元素只对应一个像素的问题，即采样率过低&lt;/strong&gt;，这种现象被称为&lt;strong&gt;Texel Footprint&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;在图像上，便体现为出现&lt;strong&gt;Moire&lt;/strong&gt;（摩尔纹）和&lt;strong&gt;Jaggies&lt;/strong&gt;（锯齿）。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./5.png&quot; alt=&quot;5&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Super sampling&lt;/strong&gt;（超采样）将会解决问题，但是太过于耗费计算资源，如何解决？&lt;strong&gt;可以在不同的位置采取不同的采样率！&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Mipmap&lt;/h2&gt;
&lt;p&gt;可以允许快速查询某个范围的近似值，但是注意只能&lt;strong&gt;方形&lt;/strong&gt;!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./6.png&quot; alt=&quot;6&quot;&gt;&lt;/p&gt;
&lt;p&gt;每次都将$2&lt;em&gt;2$的像素合成为1个新像素，例如图中$128&lt;/em&gt;128$的图像共有8层。&lt;/p&gt;
&lt;h3&gt;Compluting Mipmap Level D(计算层级)&lt;/h3&gt;
&lt;p&gt;使用相邻的屏幕纹理来计算&lt;strong&gt;Texture Footprint&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./8.png&quot; alt=&quot;8&quot;&gt;&lt;/p&gt;
&lt;p&gt;$D=log_2L$           $L=max(\sqrt{(\frac{du}{dx})^2+(\frac{dv}{dx})^2},\sqrt{(\frac{du}{dy})^2+(\frac{dv}{dy})^2})$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;$\sqrt{(\frac{du}{dx})^2+(\frac{dv}{dx})^2}$：表示沿屏幕 x方向（水平）纹理的拉伸程度。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$\sqrt{(\frac{du}{dy})^2+(\frac{dv}{dy})^2}$：表示沿屏幕 y 方向（垂直）纹理的拉伸程度。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;L：取两者的最大值，即纹理在像素空间中的最大拉伸比例。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$D=log_2L$ ：将拉伸比例 L转换为对数尺度，对应Mipmap的层级。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;若 L=1（纹理与像素1:1映射），则 D=0，使用原始纹理（Level 0）。&lt;/li&gt;
&lt;li&gt;若 L=4（纹理被拉伸4倍），则 D=2，选择Level 2的Mipmap（缩小4倍的纹理）。&lt;/li&gt;
&lt;li&gt;若 L&amp;#x3C;1（纹理被压缩），可能启用各向异性过滤或更高分辨率层级。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;mipmap计算层级效果如下图，不同距离采样率不同：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./9.png&quot; alt=&quot;9&quot;&gt;
但我们会发现边界处&lt;strong&gt;无渐变处理&lt;/strong&gt;，可以使用&lt;strong&gt;基于D值的线性插值&lt;/strong&gt;使Mipmap Level变化平滑。&lt;/p&gt;
&lt;p&gt;效果如图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./10.png&quot; alt=&quot;10&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Anisotropic Filtering（各向异性过滤）&lt;/h3&gt;
&lt;p&gt;如果Pixel Footerprint不是方形而是&lt;strong&gt;不规则图形&lt;/strong&gt;，那么mipmap的效果不太好，需要使用各项异性过滤解决。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./11.png&quot; alt=&quot;11&quot;&gt;&lt;/p&gt;
&lt;p&gt;Mipmap得到的一系列的纹理图其实就是对角线上的卫星，可以看到都是正方形的，而各向异性过滤会对把原纹理图缩放成不同大小的矩形，各向异性生成的一系列纹理图也叫&lt;strong&gt;Ripmaps&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;每一行是对纹理图做宽度压缩，每一列是对纹理图做高度压缩。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./12.png&quot; alt=&quot;12&quot;&gt;&lt;/p&gt;
&lt;p&gt;但非常不规则的图形仍然无法用各向异性过滤来解决，需要使用&lt;strong&gt;EWA Filtering&lt;/strong&gt;方法。&lt;/p&gt;
&lt;p&gt;使用椭圆形去近似不规则图形，然后多次查询得到加权平均值，但耗时长。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./13.png&quot; alt=&quot;13&quot;&gt;&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>CSAPP 第八章异常控制流总结</title><link>https://astro-pure.js.org/blog/csapp%E7%AC%AC%E5%85%AB%E7%AB%A0%E6%80%BB%E7%BB%93/csapp%E7%AC%AC%E5%85%AB%E7%AB%A0%E6%80%BB%E7%BB%93</link><guid isPermaLink="true">https://astro-pure.js.org/blog/csapp%E7%AC%AC%E5%85%AB%E7%AB%A0%E6%80%BB%E7%BB%93/csapp%E7%AC%AC%E5%85%AB%E7%AB%A0%E6%80%BB%E7%BB%93</guid><description>基于Whu计院的CSAPP第八章异常控制流总结</description><pubDate>Tue, 13 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Chapter8 异常控制流&lt;/h2&gt;
&lt;h3&gt;异常控制流&lt;/h3&gt;
&lt;h4&gt;低层次&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;异常&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;高层次&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;进程上下文切换&lt;/li&gt;
&lt;li&gt;信号&lt;/li&gt;
&lt;li&gt;非本地跳转&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;异常(exception)&lt;/h3&gt;
&lt;p&gt;异常处理程序完成后，会出现下面三种情况之一。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./8-1.png&quot; alt=&quot;exception&quot;&gt;&lt;/p&gt;
&lt;h4&gt;异常处理&lt;/h4&gt;
&lt;p&gt;系统会为每种类型异常分配&lt;strong&gt;唯一非负整数&lt;/strong&gt;的异常号(exception number)，采用异常表（跳转表）的优化方式执行间接调用异常处理程序。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./8-2.png&quot; alt=&quot;exception table&quot;&gt;&lt;/p&gt;
&lt;p&gt;异常表起始地址放在**异常表基址寄存器(exception table base register)**的特殊寄存器中。&lt;/p&gt;
&lt;p&gt;异常处理类似于过程调用，但也有不同之处：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;过程调用：将返回地址（下一条指令）压入栈中；&lt;strong&gt;异常：返回地址为当前指令或者下一条指令&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;异常：会将额外处理器状态压入栈中，便于后续恢复程序状态&lt;/li&gt;
&lt;li&gt;异常：若控制从用户程序转到内核，所有项目都压入内核栈，而不是用户栈&lt;/li&gt;
&lt;li&gt;异常处理程序运行在&lt;strong&gt;内核模式下&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;异常类别&lt;/h4&gt;
&lt;p&gt;|      类别       |          原因           | 异步/同步 |      返回行为      |            示例            |
| :-------------: | :---------------------: | :-------: | :----------------: | :------------------------: |
| 中断(interrupt) | I/O设备触发&lt;strong&gt;中断引脚&lt;/strong&gt; |   异步    |  返回到下一条指令  | 定时器中断，内核夺取控制权 |
|   陷阱(trap)    |       有意的异常        |   同步    |  返回到下一条指令  |     系统调用(syscall)      |
|   故障(fault)   |    无意可恢复的错误     |   同步    |  可能返回当前指令  |          缺页异常          |
|   终止(abort)   |     不可恢复的错误      |   同步    | 不会返回，直接终止 |       非法的内存地址       |&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;中断(interrupt)：通过向处理器引脚发出信号，将异常号放在系统总线上，**在当前指令完成执行后（异步）**调用异常中断程序。&lt;/li&gt;
&lt;li&gt;陷阱(trap)：提供系统调用，如fork,execve等，处理器提供&lt;code&gt;syscall n&lt;/code&gt;指令&lt;/li&gt;
&lt;li&gt;故障(fault)：如果程序能够修复这个故障，将返回，否则返回到内核中的abort，终止程序&lt;/li&gt;
&lt;li&gt;终止(abort)：致命错误，终止程序&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;syscall指令&lt;/h4&gt;
&lt;p&gt;linux系统中的系统调用号：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./8-3.png&quot; alt=&quot;syscall&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;寄存器%rax包含系统调用号&lt;/li&gt;
&lt;li&gt;%rdi, %rsi, %rdx, %r10, %r8, %r9 包含最多6个参数&lt;/li&gt;
&lt;li&gt;从系统调用返回时，%rax 包含返回值&lt;/li&gt;
&lt;li&gt;负数返回值表明发生了错误，对应于负的 errno&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;syscall与函数调用的异同&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;同：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;控制流转移，返回时执行下一条指令&lt;/li&gt;
&lt;li&gt;返回值存储于%rax&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;异：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;syscall由内核执行&lt;/li&gt;
&lt;li&gt;syscall的系统调用号放在%rax&lt;/li&gt;
&lt;li&gt;使用errno(全局变量)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;进程(process)&lt;/h3&gt;
&lt;p&gt;进程为每个程序提供两个关键抽象：&lt;strong&gt;逻辑控制流&lt;/strong&gt;（上下文切换机制提供）和&lt;strong&gt;私有地址空间&lt;/strong&gt;（虚拟内存机制提供）。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果两个进程的执行在时间上有重叠，它们就是&lt;strong&gt;并发的&lt;/strong&gt;,否则，它们就是&lt;strong&gt;顺序的&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;对于单核处理器，每时每刻只有一个进程在运行；不同进程执行是&lt;strong&gt;交错&lt;/strong&gt;的，并不是真正的并发执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;私有地址空间&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;进程为每个程序提供它自己的私有地址空间&lt;/li&gt;
&lt;li&gt;一般来说，和某个进程相关的内存字节不能够被其他进程读或者写。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;上下文切换&lt;/h4&gt;
&lt;p&gt;定义：上下文就是内核重新启动一个被抢占的进程所需的状态。&lt;/p&gt;
&lt;p&gt;当内核选择一个新的进程运行时，就说内核&lt;strong&gt;调度&lt;/strong&gt;了这个进程，抢占当前进程，并通过上下文切换转移到新的进程。&lt;/p&gt;
&lt;p&gt;上下文切换：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;保存当前进程的上下文&lt;/li&gt;
&lt;li&gt;恢复某个先前被抢占的进程保存的上下文&lt;/li&gt;
&lt;li&gt;将控制传递给新恢复的进程&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;系统调用错误&lt;/h3&gt;
&lt;p&gt;出错时，Linux系统级函数通常返回-1，并设置全局变量errno表示出错原因。&lt;/p&gt;
&lt;p&gt;可以通过Stevens风格的错误处理包装函数简化代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;void unix_error(char *msg) /* Unix-style error */
{
    fprintf(stderr, &quot;%s: %s\n&quot;, msg, strerror(errno));//errno为全局整数变量
    exit(1);
}

pid_t Fork(void)
{
    pid_t pid;

    if ((pid = fork()) &amp;#x3C; 0)//fork返回值小于0，表明出错
        unix_error(&quot;Fork error&quot;);
    return pid;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;进程控制&lt;/h3&gt;
&lt;h4&gt;获取进程ID&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;pid_t getpid(void);//返回当前进程的PID
pid_t getppid(void);//返回父进程的PID
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;四种进程状态&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;运行(Running)&lt;/li&gt;
&lt;li&gt;阻塞/睡眠(Blocked/Sleeping)&lt;/li&gt;
&lt;li&gt;停止(Stopped)：进程被挂起，且不被调度，可被SIGCONT信号重新运行&lt;/li&gt;
&lt;li&gt;终止/僵尸(Terminated/Zombie)：永远停止&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;创建进程&lt;/h4&gt;
&lt;p&gt;父进程通过fork函数创建一个新的子进程。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;fork函数返回&lt;strong&gt;0&lt;/strong&gt;给子进程，返回&lt;strong&gt;子进程的PID&lt;/strong&gt;给父进程，&lt;strong&gt;一次调用，两次返回！&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;相同但独立的地址空间&lt;/strong&gt;:子进程得到与父进程用户级虚拟地址空间相同（但独立，相当于复制了一份）的副本&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;共享文件&lt;/strong&gt;：子进程获得与父进程任何打开文件描述符相同的副本（如stdout文件指向屏幕，子进程也将输出指向屏幕）&lt;/li&gt;
&lt;li&gt;子、父进程PID不同&lt;/li&gt;
&lt;li&gt;两者&lt;strong&gt;并发执行，但无法预测两者执行顺序&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./8-4.png&quot; alt=&quot;8-4&quot;&gt;&lt;/p&gt;
&lt;p&gt;示例代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;int main(int argc, char** argv)
{
    pid_t pid;
    int x = 1;

    pid = Fork(); 
    if (pid == 0) {  /* Child */
        printf(&quot;child : x=%d\n&quot;, ++x); 
		return 0;
    }
    /* Parent */
    printf(&quot;parent: x=%d\n&quot;, --x); 
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;进程图&lt;/h4&gt;
&lt;p&gt;所有的拓扑排序序列都是可行的执行序列！拓扑排序相关知识参考:[拓扑排序](&lt;a href=&quot;https://oi-wiki.org/graph/topo/&quot;&gt;拓扑排序 - OI Wiki&lt;/a&gt;)&lt;/p&gt;
&lt;h4&gt;终止进程&lt;/h4&gt;
&lt;p&gt;进程终止原因：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;收到默认行为为终止进程的信号&lt;/li&gt;
&lt;li&gt;从main程序返回&lt;/li&gt;
&lt;li&gt;调用exit函数&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;void exit(int status)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;以status退出状态终止进程&lt;/li&gt;
&lt;li&gt;正常返回为0，错误时为非零&lt;/li&gt;
&lt;li&gt;另一种退出状态方法是从main程序中返回一个整数值&lt;/li&gt;
&lt;li&gt;exit调用一次但从不返回&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;回收子进程&lt;/h4&gt;
&lt;p&gt;当进程终止时，进程会保持终止状态，直至父进程回收它。此时这种进程成为僵尸态(zombie)，将会占用一定的系统资源。&lt;/p&gt;
&lt;p&gt;如果父进程终止了，内核会将子进程交由&lt;strong&gt;init&lt;/strong&gt;进程(pid=1)回收(init进程是所有进程的祖先)，但回收速度较慢。&lt;/p&gt;
&lt;h5&gt;waitpid函数&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;pid_t waitpid(pid_t pid,int *statusp,int options);
//返回：成功返回子进程PID；WNOHANG则返回0；其他错误则返回-1
//如果父进程所有子进程全部被回收，则立即返回
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;1.判定等待集合的成员(pid)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;pid&gt;0：等待pid子进程&lt;/li&gt;
&lt;li&gt;pid=-1：等待该父进程的所有子进程&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;2.修改默认行为(options)（&lt;strong&gt;了解即可&lt;/strong&gt;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;WNOHANG：如果集合中任何子进程都还没终止，就立即返回&lt;/li&gt;
&lt;li&gt;WUNTRACED：挂起调用进程的执行，直到集合中某个进程变成已终止或者被停止&lt;/li&gt;
&lt;li&gt;WCONTINUED：挂起调用进程的执行，直到集合中某个正在运行的进程终止或某个被停止的进程收到SIGCONT信号继续执行&lt;/li&gt;
&lt;li&gt;WNOHANG|WUNTRACED：立即返回。集合中进程都没停止或者终止，返回0；如果一个子进程停止或终止，返回该子进程pid&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;3.检查已回收子进程的退出状态（&lt;strong&gt;了解即可&lt;/strong&gt;)&lt;/p&gt;
&lt;p&gt;WIFEXITED, WEXITSTATUS, WIFSIGNALED, WTERMSIG, WIFSTOPPED, WSTOPSIG, WIFCONTINUED&lt;/p&gt;
&lt;p&gt;4.错误条件&lt;/p&gt;
&lt;p&gt;如果调用进程无子进程，waitpid返回-1，设置errno为ECHILD。如果waitpid函数被信号中断，返回-1,设置errno为EINTR。&lt;/p&gt;
&lt;h5&gt;wait函数&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;所等待的集合是父进程所有的子进程。&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;pid_t wait(int *statusp);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;void fork9() {
    int child_status;

    if (fork() == 0) {
        printf(&quot;HC: hello from child\n&quot;);
		exit(0);//子进程终止
    } else {
        printf(&quot;HP: hello from parent\n&quot;);
        wait(&amp;#x26;child_status);//等待子进程，终止时回收
        printf(&quot;CT: child has terminated\n&quot;);
    }
    printf(&quot;Bye\n&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;void fork10() {
    pid_t pid[N];
    int i, child_status;

    for (i = 0; i &amp;#x3C; N; i++)
        if ((pid[i] = fork()) == 0) {//子进程
 			sleep(3);//休眠3秒
            exit(100+i); /* 子进程返回，退出码100+i */
        }
    for (i = 0; i &amp;#x3C; N; i++) { //父进程
        pid_t wpid = wait(&amp;#x26;child_status);//等待某个子进程终止
        if (WIFEXITED(child_status))//正常终止
            printf(&quot;Child %d terminated with exit status %d\n&quot;,
                   wpid, WEXITSTATUS(child_status));
        else
            printf(&quot;Child %d terminate abnormally\n&quot;, wpid);//异常终止
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;休眠进程&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;unsigned int sleep(unsigned int secs);&lt;/code&gt;函数可以设置进程休眠秒数，若时间已到，返回0，否则返回还剩休眠秒数。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;int pause(void);&lt;/code&gt;直接调用函数休眠，直至收到一个信号。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;加载并运行程序&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;execve&lt;/code&gt;函数在&lt;strong&gt;当前进程&lt;/strong&gt;(相当于替换当前进程)的上下文加载并运行一个新程序,&lt;strong&gt;调用一次且从不返回（除非有错误）&lt;/strong&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;int execve(const char *filename,const char *argv[],const char *envp[]);
//成功不返回，错误返回-1
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;filename:可执行文件，argv：带参数列表（&lt;strong&gt;argv[0]=filename，argv以null结尾&lt;/strong&gt;)，envp：环境变量列表(&lt;strong&gt;以null结尾，是name=value的键值对&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;覆盖原进程的代码段、数据段和栈，保留PID、打开的文件和信号上下文&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./8-5.png&quot; alt=&quot;8-5&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;argc给出argv[]数组中非空指针的数量&lt;/li&gt;
&lt;li&gt;argv指向argv[]数组中第一个条目&lt;/li&gt;
&lt;li&gt;environ指向envp[]数组中的第一个条目&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Shell程序&lt;/h3&gt;
&lt;p&gt;一个简单shell程序的main函数示例：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &quot;csapp.h&quot;
#define MAXARGS   128

/* Function prototypes */
void eval(char *cmdline);
int parseline(char *buf, char **argv);
int builtin_command(char **argv); 

int main() 
{
    char cmdline[MAXLINE]; /* 输入命令行 */

    while (1) {
        /* 循环读取*/
        printf(&quot;&gt; &quot;);                   
        Fgets(cmdline, MAXLINE, stdin); //通过Fgets获取一行输入，MAXLINE可以防止缓存区溢出
        if (feof(stdin))
            exit(0);
        /* 处理命令 */
        eval(cmdline);
    } 
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;eval函数示例:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;void eval(char *cmdline) 
{
    char *argv[MAXARGS]; /* Argument list execve() 带参数列表 */
    char buf[MAXLINE];   /* Holds modified command line 保存了分隔处理后的输入命令*/
    int bg;              /* 作业在前台还是后台运行 */
    pid_t pid;           /* Process id 进程号 */
    
    strcpy(buf, cmdline);//将cmdline先复制到buf中
    bg = parseline(buf, argv); //调用parseline分隔处理命令并返回前台还是后台运行进程
    if (argv[0] == NULL)  
		return;   /* 空命令(filename为空)直接返回 */

    if (!builtin_command(argv)) { //返回0则创建一个子进程
        if ((pid = Fork()) == 0) {   /* 如果为子进程 */
            if (execve(argv[0], argv, environ) &amp;#x3C; 0) {//如果无法找到该命令
                printf(&quot;%s: Command not found.\n&quot;, argv[0]);
                exit(0);//终止子进程
            }
        }

	/* 父进程等待子进程终止回收 */
	if (!bg) {//前台运行
	    int status;
	    if (waitpid(pid, &amp;#x26;status, 0) &amp;#x3C; 0)
			unix_error(&quot;waitfg: waitpid error&quot;);
	}
	else //后台运行，无需等待子进程终止,注意这里无法回收后台运行的子进程，变为僵尸进程！！！
	    printf(&quot;%d %s&quot;, pid, cmdline);
    }
    return;
}

/* 如果第一个参数是内置命令，请运行它并返回true */
int builtin_command(char **argv) 
{
    if (!strcmp(argv[0], &quot;quit&quot;)) /* quit command */
		exit(0);  
    if (!strcmp(argv[0], &quot;&amp;#x26;&quot;))    /* 如果输入的是单独的&amp;#x26;,返回1，不创建子进程*/
		return 1;
    return 0;                     /* 不是一个内置的命令，创建子进程执行*/
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;int parseline(char *buf, char **argv) 
{
    char *delim;         /* 指向第一个空格分隔符 */
    int argc;            /* argv中非空指针的数量 */
    int bg;              /* 是否为后台运行进程 */

    buf[strlen(buf)-1] = &apos; &apos;;  /* 代替&apos;\n&apos;为空格 */
    while (*buf &amp;#x26;&amp;#x26; (*buf == &apos; &apos;)) /* 忽略前置空格 */
	buf++;

    /* 创建argv列表*/
    argc = 0;
    while ((delim = strchr(buf, &apos; &apos;))) {// 查找下一个空格
		argv[argc++] = buf;// 存储当前参数
		*delim = &apos;\0&apos;;// 用 &apos;\0&apos; 替换空格,标记字符串结束,使 argv[argc] 成为合法字符串
		buf = delim + 1;// 移动到下一个参数，从当前空格第二个字符开始
		while (*buf &amp;#x26;&amp;#x26; (*buf == &apos; &apos;)) /* 忽略空格 */
            buf++;
    }
    argv[argc] = NULL; // argv 必须以 NULL 结尾
    
    if (argc == 0)  /* 忽略空白命令行 */
	return 1;

    /* 是否后台运行，有&amp;#x26;结尾则是后台运行 */
    if ((bg = (*argv[argc-1] == &apos;&amp;#x26;&apos;)) != 0)
	argv[--argc] = NULL;

    return bg;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;信号&lt;/h3&gt;
&lt;p&gt;信号由内核发送（有时由其他进程请求）到目标进程，Linux系统支持30种信号。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;发送信号：&lt;strong&gt;内核检测到了一个系统事件或者一个进程调用了kill函数，显示发送一个信号给目标进程（可以发送给自己）&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;接收信号：目的进程被内核强迫以某种方式对信号作出反应&lt;/li&gt;
&lt;li&gt;一个发出而没有接收的信号叫做**待处理(pending)**信号&lt;/li&gt;
&lt;li&gt;任何时刻，&lt;strong&gt;每个种类至多只有一个待处理信号&lt;/strong&gt;，其余发送的信号都将被丢弃而不会排队，可以利用这种机制有选择性地阻塞接受某种信号&lt;/li&gt;
&lt;li&gt;一个进程可以&lt;strong&gt;阻塞&lt;/strong&gt;接收某种信号，一个待处理信号最多只能被接受一次&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Pending，blocked位向量（信号掩码）&lt;/h4&gt;
&lt;p&gt;内核维护每个进程的上下文中的&lt;strong&gt;pending&lt;/strong&gt; &lt;strong&gt;和&lt;/strong&gt; &lt;strong&gt;blocked&lt;/strong&gt; 位向量&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;pending：待处理信号集合
&lt;ul&gt;
&lt;li&gt;只要传递了一个类型为 k 的信号，内核就会设置 &lt;strong&gt;pending&lt;/strong&gt; 中的第 k 位&lt;/li&gt;
&lt;li&gt;只要接收了一个类型为 k 的信号，内核就会清除 &lt;strong&gt;pending&lt;/strong&gt; 中的第 k 位&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;blocked：阻塞信号集合，可以通过sigprocmask函数设置或者清除这些位&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;发送信号&lt;/h4&gt;
&lt;h5&gt;进程组&lt;/h5&gt;
&lt;p&gt;每个进程都只属于一个进程组。由一个正整数进程组ID标识。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;pid_t getpgrp(void);//函数返回调用进程的进程组ID
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;默认情况，子进程与父进程同属于一个进程组&lt;/strong&gt;，但也可以通过&lt;code&gt;setpgid&lt;/code&gt;函数来改变进程组。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;int setpgid(pid_t pid,pid_t pgid);//若成功返回0，错误返回-1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将进程pid的进程组改为pgid。&lt;strong&gt;若pid=0,则使用当前进程的pid；若pgid=0，则用pid指定的进程PID作为进程组ID。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;例如：进程15213调用setpgid(0,0)，则会创建一个进程组ID为15213的进程组，并将进程15213加入这个进程组中。&lt;/p&gt;
&lt;h5&gt;/bin/kill 程序发送信号&lt;/h5&gt;
&lt;p&gt;&lt;code&gt;/bin/kill -9 ID&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;信号9为SIGKILL。若ID&gt;0 则杀死进程ID，若ID&amp;#x3C;0，则杀死进程组ID中的所有进程。&lt;/p&gt;
&lt;h5&gt;从键盘发送信号&lt;/h5&gt;
&lt;p&gt;同一时刻，至多只有一个前台作业(&lt;strong&gt;job&lt;/strong&gt;)和0/多个后台作业。同一个前台作业可以&lt;strong&gt;包含1个或者多个进程，它们的进程组ID都相同&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;键入&lt;/strong&gt; **ctrl-c (ctrl-z)**会导致内核发送 &lt;strong&gt;SIGINT&lt;/strong&gt;（&lt;strong&gt;SIGTSTP&lt;/strong&gt;）信号到前台进程组中的每个作业&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SIGINT默认终止每个进程&lt;/li&gt;
&lt;li&gt;SIGTSTP默认停止/暂停每个进程&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;kill函数发送信号&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;int kill(pid_t pid , int sig);//成功返回0，错误返回-1
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;pid&gt;0：发送信号号码sig给进程pid&lt;/li&gt;
&lt;li&gt;pid=0：发送信号sig给调用进程所在进程组中的所有进程（包括调用进程）&lt;/li&gt;
&lt;li&gt;pid&amp;#x3C;0：发送信号给进程组|pid|中的每个进程&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;alarm函数发送信号（上课未讲）&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;unsigned int alarm(unsigned int secs);//返回前一次闹钟剩余秒数，若未设定闹钟，则返回0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;alarm&lt;/code&gt;函数安排内核在secs秒后发送一个SIGALRM信号给调用进程。&lt;/p&gt;
&lt;p&gt;对alarm的调用将取消任何待处理（pending）闹钟，并且返回任何待处理的闹钟在被发送前还剩的秒数（无待处理闹钟则返回0）。&lt;/p&gt;
&lt;h4&gt;接受信号&lt;/h4&gt;
&lt;p&gt;内核把进程p&lt;strong&gt;从内核模式切换到用户模式&lt;/strong&gt;时，会检测进程p的未被阻塞的待处理信号集合。若集合非空，则会选取某个信号k（通常为最小的k），并强制进程p接受信号k，完成接收后继续p的逻辑控制流的下一条指令。（&lt;strong&gt;也就是说每次只会接受一个信号&lt;/strong&gt;）&lt;/p&gt;
&lt;h5&gt;信号的默认行为（系统预设）：&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;忽略信号&lt;/li&gt;
&lt;li&gt;终止进程&lt;/li&gt;
&lt;li&gt;终止进程并转储内存&lt;/li&gt;
&lt;li&gt;进程停止直到被SIGCONT信号重启&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;接收信号可能反应方式&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;忽略信号&lt;/li&gt;
&lt;li&gt;终止进程（可能转储内存）&lt;/li&gt;
&lt;li&gt;捕获信号，执行&lt;strong&gt;信号处理程序&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;自定义信号行为&lt;/h5&gt;
&lt;p&gt;SIGSTOP和SIGKILL这两个信号无法修改默认行为。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;sighandler_t signal(int signum , sighandler_t handler);
//若成功返回前次处理程序指针，出错则为SIG_ERR(不设置errno)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;handler不同取值：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SIG_IGN：忽略signum信号。&lt;/li&gt;
&lt;li&gt;SIG_DFL：signum信号恢复默认行为。&lt;/li&gt;
&lt;li&gt;其他情况：handler为用户定义的&lt;strong&gt;信号处理程序&lt;/strong&gt;地址，把处理程序传给signal函数行为叫做&lt;strong&gt;设置信号处理程序&lt;/strong&gt;，调用信号处理程序叫做&lt;strong&gt;捕获信号&lt;/strong&gt;，执行信号处理程序叫做&lt;strong&gt;处理信号&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;当信号处理程序执行return语句时，控制传递回控制流中进程被中断位置的指令（&lt;strong&gt;就是会从接受信号的地方继续执行&lt;/strong&gt;）。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;void sigint_handler(int sig) /* 自定义信号处理程序*/
{
    printf(&quot;So you think you can stop the bomb with ctrl-c, do you?\n&quot;);
    sleep(2);
    printf(&quot;Well...&quot;);
    fflush(stdout);
    sleep(1);
    printf(&quot;OK. :-)\n&quot;);
    exit(0);
}

int main()
{
    /* Install the SIGINT handler */
    if (signal(SIGINT, sigint_handler) == SIG_ERR)//当用户按下Ctrl+C后内核发送SIGINT信号
        unix_error(&quot;signal error&quot;);

    /* Wait for the receipt of a signal */
    pause();

    return 0;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;信号处理程序可以被其他信号处理程序中断（即可以嵌套）&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./8-6.png&quot; alt=&quot;8-6&quot;&gt;&lt;/p&gt;
&lt;h4&gt;阻塞和接触信号&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;隐式阻塞机制&lt;/strong&gt;：内核默认阻塞任何当前处理程序&lt;strong&gt;正在处理信号类型的待处理信号&lt;/strong&gt;。例如，一个 SIGINT 处理程序不会被另一个 SIGINT 信号中断。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;显式阻塞机制&lt;/strong&gt;：使用&lt;strong&gt;sigprocmask&lt;/strong&gt;函数和它的辅助函数明确地阻塞和接触阻塞选定的信号。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;int sigprocmask(int how,const sigset_t *set,sigset_t *oldset);
int sigemptyset(sigset_t *set);//初始化为空集合
int sigfillset(sigset_t *set);//将所有信号编号添加到集合中
int sigaddset(sigset_t *set,int signum);//将特定信号编号添加到集合中
int sigdelset(sigset_t *set,int signum);//从集合中删除特定信号编号
//成功返回0，出错返回-1
int sigismember(const sigset_t *set,int signum);//是否是集合中的成员
//若signum是set的成员则为1，如果不是则为0，出错为-1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;how的值:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SIG_BLOCK:将set中的信号添加到blocked中&lt;/li&gt;
&lt;li&gt;SIG_UNBLOCK:将blocked中删除set中的信号&lt;/li&gt;
&lt;li&gt;SIG_SETMASK:block=set&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果oldset非空，那么**blocked位向量（也称为信号掩码）**之前的值保存在oldset中。&lt;/p&gt;
&lt;p&gt;用sigprocmask来临时阻塞接受SIGINT信号代码示例：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;sigset_t mask, prev_mask;

Sigemptyset(&amp;#x26;mask);//将mask集合初始化为空
Sigaddset(&amp;#x26;mask, SIGINT);//添加SIGINT信号进入mask集合

/* 阻塞SIG_INT信号，并将blocked位向量保存在prev_mask集合中 */
Sigprocmask(SIG_BLOCK, &amp;#x26;mask, &amp;#x26;prev_mask);

/* 该代码区域将不会被SIG_INT信号中断 */

/* 恢复block=prev_mask，解除阻塞SIG_INT信号 */
Sigprocmask(SIG_SETMASK, &amp;#x26;prev_mask, NULL);

&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;安全的信号处理（了解即可）&lt;/h4&gt;
&lt;p&gt;教材8.5.5节有详细说明，期末考试不涉及。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./8-7.png&quot; alt=&quot;8-7&quot;&gt;&lt;/p&gt;
&lt;h4&gt;正确的信号处理&lt;/h4&gt;
&lt;p&gt;未处理的信号不排队，直接舍弃，每种类型最多只能有一个未处理的信号，所以&lt;strong&gt;不能用信号对其他进程中发生的事件计数，如子进程终止的次数&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;必须等待所有终止的子进程，使用wait回收所有已终止的子进程！&lt;/p&gt;
&lt;h4&gt;可移植的信号处理&lt;/h4&gt;
&lt;p&gt;sigaction函数：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;int sigaction(int signum, struct sigaction *act,struct sigaction *oldact);
//成功返回0，错误返回-1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用户可以明确指定信号处理语义，但我们一般使用Signal包装函数。&lt;/p&gt;
&lt;h4&gt;同步流&lt;/h4&gt;
&lt;p&gt;利用原子锁，确保一个事务的原子性，用信号的阻塞机制。先lock，再工作，再unlock。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &quot;csapp.h&quot;

void initjobs()
{
}

void addjob(int pid)
{
}

void deletejob(int pid)
{
}

void handler(int sig)
{
    int olderrno = errno;
    sigset_t mask_all, prev_all;
    pid_t pid;

    Sigfillset(&amp;#x26;mask_all);//阻塞所有信号
    while ((pid = waitpid(-1, NULL, 0)) &gt; 0) { /* 进程终止 */
        Sigprocmask(SIG_BLOCK, &amp;#x26;mask_all, &amp;#x26;prev_all);//阻塞所有信号
        deletejob(pid); /* 删除列表 */
        Sigprocmask(SIG_SETMASK, &amp;#x26;prev_all, NULL);//block = prev_all
    }
    if (errno != ECHILD)
        Sio_error(&quot;waitpid error&quot;);
    errno = olderrno;
}
    
int main(int argc, char **argv)
{
    int pid;
    sigset_t mask_all, mask_one, prev_one;

    Sigfillset(&amp;#x26;mask_all);//将所有信号添加到集合mask_all中
    Sigemptyset(&amp;#x26;mask_one);//设置空集合
    Sigaddset(&amp;#x26;mask_one, SIGCHLD);//添加SIGCHLD信号
    Signal(SIGCHLD, handler);//绑定SIGCHLD信号到信号处理程序handler
    initjobs(); /* 初始化作业列表 */
    /*
    	阻塞子进程发的信号，先addjob,再解除阻塞，接收子进程发的信号，deletejob。
    */
    while (1) {
        Sigprocmask(SIG_BLOCK, &amp;#x26;mask_one, &amp;#x26;prev_one); /* 阻塞SIG_CHLD信号 */
        if ((pid = Fork()) == 0) { /* 如果是子进程中 */
            Sigprocmask(SIG_SETMASK, &amp;#x26;prev_one, NULL); /* 解除阻塞SIG_CHLD信号，子进程会继承父进程的阻塞列表 */
            Execve(&quot;/bin/date&quot;, argv, NULL);//execve执行替换自身进程
        }
        Sigprocmask(SIG_BLOCK, &amp;#x26;mask_all, NULL); /* 父进程，阻塞所有信号，不接收子进程发来的SIG_CHLD信号 */  
        addjob(pid);  /* 先添加到作业列表中 */
        Sigprocmask(SIG_SETMASK, &amp;#x26;prev_one, NULL);  /* 解除阻塞 SIGCHLD 信号*/
    }
    exit(0);
}
/* $end procmask2 */
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;显示等待信号&lt;/h4&gt;
&lt;p&gt;几种显式等待信号的方式：&lt;/p&gt;
&lt;p&gt;1.while轮询&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;while(!pid)
    ;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;程序正确但太浪费系统资源。&lt;/p&gt;
&lt;p&gt;2.sleep休眠函数&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;while(!pid)
    sleep(1);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;每次等待1秒再查询检测，无法及时检测信号。&lt;/p&gt;
&lt;p&gt;3.pause()函数&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;while(!pid)
    pause();
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;当&lt;code&gt;!pid&lt;/code&gt;为真时（即pid还是0），准备进入pause()&lt;/li&gt;
&lt;li&gt;但就在这之间（检查pid后，调用pause前），子进程终止并发送了SIGCHLD信号&lt;/li&gt;
&lt;li&gt;由于pause()尚未被调用，信号会被直接处理（假设有SIGCHLD处理程序）&lt;/li&gt;
&lt;li&gt;然后当pause()最终被调用时，再也没有新的信号来唤醒它&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4.sigsuspend函数&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;int sigsuspend(const sigset_t *mask);
//相当于原子版本：
sigprocmask(SIG_BLOCK,&amp;#x26;mask,&amp;#x26;prev);//注意就是SIG_BLOCK
pause();
sigprocmask(SIG_SETMASK,&amp;#x26;prev,NULL);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;sigsuspend相当于sigprocmask（第三行）和pause函数（第四行）同时执行（原子性），消除潜在竞争。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下面是一个利用sigsuspend函数来等待信号的示例：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &quot;csapp.h&quot;

volatile sig_atomic_t pid;

void sigchld_handler(int s)
{
    int olderrno = errno;
    pid = Waitpid(-1, NULL, 0);
    errno = olderrno;
}

void sigint_handler(int s)
{
}

int main(int argc, char **argv) 
{
    sigset_t mask, prev;//两个集合

    Signal(SIGCHLD, sigchld_handler);//绑定信号处理程序
    Signal(SIGINT, sigint_handler);//绑定信号处理程序
    Sigemptyset(&amp;#x26;mask);//设置mask集合为空
    Sigaddset(&amp;#x26;mask, SIGCHLD);//加入SIGCHLD到mask集合

    while (1) {
        Sigprocmask(SIG_BLOCK, &amp;#x26;mask, &amp;#x26;prev); /* 阻塞 SIGCHLD，防止在Sigsuspend之前SIGCHLD信号到达 */
        if (Fork() == 0) /* 子进程 */
            exit(0);

        /* 等待SIGCHLD信号接收*/
        pid = 0;//设置pid=0
        while (!pid) //pid=0时进入
            Sigsuspend(&amp;#x26;prev);//注意传入的参数是prev
        /*Sigsuspend可以等效为下列代码：
        	sigprocmask(SIG_SETMASK,&amp;#x26;prev,&amp;#x26;prev_2); prev为不包含SIGCHLD的集合，解除阻塞
			pause(); 临时打开了SIGCHLD信号，允许接收SIGCHLD信号
			sigprocmask(SIG_SETMASK,&amp;#x26;prev_2,NULL);	再次阻塞SIGCHLD信号
        */

        /* 解除阻塞 SIGCHLD */
        Sigprocmask(SIG_SETMASK, &amp;#x26;prev, NULL); 

        /* Do some work after receiving SIGCHLD */
        printf(&quot;.&quot;);
    }
    exit(0);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;非本地跳转&lt;/h3&gt;
&lt;p&gt;可以将控制从一个函数转到另一个正在执行的函数，无需经过正常调用-返回。通过&lt;strong&gt;setjmp&lt;/strong&gt;和&lt;strong&gt;longjmp&lt;/strong&gt;函数实现的。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;int setjmp(jmp_buf env);
int sigsetjmp(sigjmp_buf env,int savesigs);
//返回0
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;在调用&lt;strong&gt;longjmp之前&lt;/strong&gt;调用，标识后续longjmp的&lt;strong&gt;返回位置&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;调用&lt;strong&gt;一次&lt;/strong&gt;，返回&lt;strong&gt;一次或者多次&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;实现机制：将当前&lt;strong&gt;寄存器上下文、栈指针和程序计数器&lt;/strong&gt;存储在跳转缓冲区 &lt;strong&gt;env&lt;/strong&gt; 中，从而记录当前位置&lt;/li&gt;
&lt;li&gt;注意不可以用变量接收返回值，但可以使用switch或者条件语句的测试条件&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;void longjmp(jmp_buf env,int retval);
void siglongjmp(sigjmp_buf env,int retval);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;跳转到缓冲区env的setjmp函数处，并且setjmp函数&lt;strong&gt;返回retval&lt;/strong&gt;而不是0&lt;/li&gt;
&lt;li&gt;调用&lt;strong&gt;一次&lt;/strong&gt;，从不返回&lt;/li&gt;
&lt;li&gt;实现机制：从跳转缓冲区env中恢复&lt;strong&gt;寄存器上下文、栈指针、基指针和程序计数器&lt;/strong&gt;，将**%eax**（返回值寄存器）设置为 &lt;strong&gt;retval&lt;/strong&gt;，跳转到跳转缓冲区env中的setjmp函数处。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;非本地跳转代码示例：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &quot;csapp.h&quot;

jmp_buf buf;

int error1 = 0; 
int error2 = 1;

void foo(void), bar(void);

int main() 
{
    switch(setjmp(buf)) {
    case 0: //初次调用保存buf时返回值默认为0，调用foo()函数
	foo();
        break;
    case 1: //foo中返回
		printf(&quot;Detected an error1 condition in foo\n&quot;);
        break;
    case 2: //bar中返回
		printf(&quot;Detected an error2 condition in foo\n&quot;);
        break;
    default:
		printf(&quot;Unknown error condition in foo\n&quot;);
    }
    exit(0);
}

/* Deeply nested function foo */
void foo(void) 
{
    if (error1)
		longjmp(buf, 1); //回到buf中的setjmp处，并且setjmp返回1
    bar();
}

void bar(void) 
{
    if (error2)
		longjmp(buf, 2); //回到buf中的setjmp处，并且setjmp返回2
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一个restart程序示例：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &quot;csapp.h&quot;

sigjmp_buf buf;

void handler(int sig) 
{
    siglongjmp(buf, 1);//程序接收到SIGINT信号后，返回到sigsetjmp处，进入重启状态
}

int main() 
{
    if (!sigsetjmp(buf, 1)) {//如果是第一次调用sigsetjmp，设置buf并且绑定信号到handler
        Signal(SIGINT, handler);//更改SIGINT的默认行为
		Sio_puts(&quot;starting\n&quot;);
    }
    else //其他时候从siglongjmp返回到sigsetjmp，重启状态
		Sio_puts(&quot;restarting\n&quot;);

    while(1) {
		Sleep(1);
        Sio_puts(&quot;processing...\n&quot;);
    }
    exit(0); /* Control never reaches here */
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>GAMES101 作业2</title><link>https://astro-pure.js.org/blog/games101/games101-pa2</link><guid isPermaLink="true">https://astro-pure.js.org/blog/games101/games101-pa2</guid><description>GAMES101 作业2解析</description><pubDate>Thu, 08 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、作业要求&lt;/h2&gt;
&lt;p&gt;1.实现三角形栅格化算法&lt;/p&gt;
&lt;p&gt;2.确定测试点是否在三角形内&lt;/p&gt;
&lt;p&gt;3.实现z-buffer算法&lt;/p&gt;
&lt;p&gt;4.super-sampling 2*2 采样减少锯齿感（提高）&lt;/p&gt;
&lt;h2&gt;二、实现三角形栅格化算法&lt;/h2&gt;
&lt;p&gt;首先算出bounding_box的大小和坐标位置&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;int min_x = INT_MAX,min_y = INT_MAX,max_x = 0,max_y = 0;
    min_x = std::min({v[0].x(),v[1].x(),v[2].x()});
    max_x = std::max({v[0].x(),v[1].x(),v[2].x()});
    min_y = std::min({v[0].y(),v[1].y(),v[2].y()});
    max_y = std::max({v[0].y(),v[1].y(),v[2].y()});
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三、测试点是否在三角形内&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;static bool insideTriangle(int x, int y, const Vector3f* _v)
{   
    // TODO : Implement this function to check if the point (x, y) is inside the triangle represented by _v[0], _v[1], _v[2]
    Vector2f a(_v[0].x()-x,_v[0].y()-y);
    Vector2f b(_v[1].x()-x,_v[1].y()-y);
    Vector2f c(_v[2].x()-x,_v[2].y()-y);

    float cross1 = a.x() * b.y() - a.y() * b.x();
    float cross2 = c.x() * a.y() - c.y() * a.x();
    float cross3 = b.x() * c.y() - b.y() * c.x();

    return (cross1&gt;0 &amp;#x26;&amp;#x26; cross2&gt;0 &amp;#x26;&amp;#x26; cross3&gt;0 ) || (cross1&amp;#x3C;0 &amp;#x26;&amp;#x26; cross2&amp;#x3C;0 &amp;#x26;&amp;#x26; cross3&amp;#x3C;0);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;二维叉乘公式：$a\times b = x_1y_2-x_2y_1$&lt;/p&gt;
&lt;p&gt;如果和三个点叉乘均同号，则在三角形内&lt;/p&gt;
&lt;h2&gt;四、z-buffer算法&lt;/h2&gt;
&lt;p&gt;接着坐标遍历bounding_box中所有的点，计算出每个点的深度，如果当前点靠前则更新颜色&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;auto[alpha, beta, gamma] = computeBarycentric2D(x+step[k][0], y+step[k][1], t.v);
float w_reciprocal = 1.0/(alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());
float z_interpolated = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();
z_interpolated *= w_reciprocal;
if(depth_buf[getindex(x,y)] == std::numeric_limits&amp;#x3C;float&gt;::infinity() || z_interpolated &gt; depth_buf[getindex(x,y)]){
    depth_buf[getindex(x,y)] = z_interpolated;
    frame_buf[getsuperindex(x,y)] = t.getColor();
    Eigen::Vector3f point(x*1.0,y*1.0,1.0);
    set_pixel(point,color);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;五、Super_sampling&lt;/h2&gt;
&lt;p&gt;要求我们使用4x4采样，即每个点需要维护四个depth和frame&lt;/p&gt;
&lt;p&gt;创建depth和frame&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;std::vector&amp;#x3C;float&gt; super_depth_buf;
std::vector&amp;#x3C;Eigen::Vector3f&gt; super_frame_buf;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;获取坐标函数&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;int rst::rasterizer::getsuperindex(int x,int y,int k){
    return 4*get_index(x,y)+k;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;遍历bounding_box中的每个点&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;for(int k = 0;k&amp;#x3C;4;k++){
    if(!insideTriangle(x+step[k][0],y+step[k][1],t.v)) continue;
    //find interpolated depth value in this location
    auto[alpha, beta, gamma] = computeBarycentric2D(x+step[k][0], y+step[k][1], t.v);
    float w_reciprocal = 1.0/(alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());
    float z_interpolated = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();
     z_interpolated *= w_reciprocal;

    if(super_depth_buf[getsuperindex(x,y,k)] == std::numeric_limits&amp;#x3C;float&gt;::infinity() || z_interpolated &gt; super_depth_buf[getsuperindex(x,y,k)]){
         count++;
         super_depth_buf[getsuperindex(x,y,k)] = z_interpolated;
         super_frame_buf[getsuperindex(x,y,k)] = t.getColor();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后再遍历设定颜色，注意颜色要除以4&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;for(int x = min_x;x&amp;#x3C;=max_x;x++){
        for(int y = min_y;y&amp;#x3C;=max_y;y++){
            Vector3f color = (super_frame_buf[getsuperindex(x,y,0)]+super_frame_buf[getsuperindex(x,y,1)]+super_frame_buf[getsuperindex(x,y,2)]+
                super_frame_buf[getsuperindex(x,y,3)])/4.0;
            Eigen::Vector3f point(x*1.0,y*1.0,1.0);
            set_pixel(point,color);
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最终效果&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./games101pa2-1.png&quot; alt=&quot;1&quot;&gt;&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>DataLab实验报告</title><link>https://astro-pure.js.org/blog/csapp%E5%AE%9E%E9%AA%8C/data-lab%E5%AE%9E%E9%AA%8C%E6%8A%A5%E5%91%8A</link><guid isPermaLink="true">https://astro-pure.js.org/blog/csapp%E5%AE%9E%E9%AA%8C/data-lab%E5%AE%9E%E9%AA%8C%E6%8A%A5%E5%91%8A</guid><description>CSAPP DataLab实验解析</description><pubDate>Wed, 05 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1.bitOr&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int bitOr(int x, int y) {
  return ~(~x &amp;#x26; ~y);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用&amp;#x26;和~实现或运算。或运算为假当且仅当x,y均为假，只需要对~x和~y进行与运算即可判断是否x,y都为假&lt;/p&gt;
&lt;h2&gt;2.anyEvenBit&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int anyEvenBit(int x) {
  int mask = 0x55 | 0x55&amp;#x3C;&amp;#x3C;8;
  int mask2 = mask &amp;#x3C;&amp;#x3C; 16;
  return !!(x &amp;#x26;(mask | mask2));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只需要用一个所有偶数位为1的掩码对x进行与运算，如果为0，则偶数位上不存在1，否则存在&lt;/p&gt;
&lt;h2&gt;3.rotateLeft&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int rotateLeft(int x, int n) {
  return (x&amp;#x3C;&amp;#x3C;n|(x&gt;&gt;(32+(~n+1)) &amp;#x26; ((1&amp;#x3C;&amp;#x3C;n)+(~1+1))));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;题目要求实现一个环形的左移，即要考虑溢出的高位应该填充到低位上。&lt;code&gt;x&amp;#x3C;&amp;#x3C;n&lt;/code&gt;会将高位截断，再思考如何将溢出的填充到低位上。可以将x右移$32-n$位，特别注意如果x为负数，应该将高位全部设置为0&lt;/p&gt;
&lt;h2&gt;4.greatestBitPos&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int greatestBitPos(int x) {
   int m=!x+(~0);
   int sign=x&gt;&gt;31;
   x |= x&gt;&gt;1;
   x |= x&gt;&gt;2;
   x |= x&gt;&gt;4;
   x |= x&gt;&gt;8;
   x |= x&gt;&gt;16;
   x=(x&gt;&gt;1)+1;
   return (x&amp;#x26;m)|((1&amp;#x3C;&amp;#x3C;31)&amp;#x26;sign);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当x为0时,$m=1-1=0,sign=0$ 返回值为0
当$x&gt;0$时，$m=0-1=-1,sign=0$ ,接着将x右移1、2、4、8、16位，将x最高位后的位全部设置为1，最后将x&gt;&gt;1+1取出x的最高位，返回x&amp;#x26;-1
当$x&amp;#x3C;0$时，$m=0-1=-1,sign=-1$，接着将x右移1、2、4、8、16位，将x最高位后的位全部设置为1，最后将x&gt;&gt;1+1取出x的最高位，返回x&amp;#x26;1并加上负号&lt;/p&gt;
&lt;h2&gt;5.leastBitPos&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int leastBitPos(int x) {
    return x &amp;#x26; (~x+1);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;根据lowerbit的结论,&lt;strong&gt;lowerbit=n&amp;#x26;(-n)&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;6.subOk&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int subOK(int x, int y) {
   int sum = x + (~y + 1);
   int sign = (x ^ y) &amp;#x26; (x ^ sum);
   return !(sign &gt;&gt; 31);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;需要检验x-y是否溢出！
溢出只有两种情况,一是x为正，y为负，sum为负，另一种是x为负，y为正，sum为正，可以观察到：x与y异号并且x与sum异号，可以使用异或检查符号位是否满足溢出条件&lt;/p&gt;
&lt;h2&gt;7.satMul3&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int satMul3(int x) {
   int two = x + x;
   int three = two + x;
   int mask = ((x^two) | (x^three))&gt;&gt;31;
   return (mask | three) ^ (mask &amp;#x26; ((x&gt;&gt;31) ^ (1&amp;#x3C;&amp;#x3C;31)));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;首先我们考虑求出$3x$，思路易得，先求出$2x$，再加上$x$得到$3x$。
接着我们需要思考是否溢出，可以将3x变为x+2x = 3x，即可化成x+y=sum是否溢出的情形。&lt;/p&gt;
&lt;h2&gt;8.divpwr2&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int divpwr2(int x, int n) {
   int sign = (x&gt;&gt;31);
   int mask = ((1&amp;#x3C;&amp;#x3C;n)+(~1+1));
   int res = ( x + (mask&amp;#x26;sign) )&gt;&gt; n;
    return res;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;除以2的n次方，可以等效为二进制右移n位。特别注意x为负数时，应该向上舍入。当x&amp;#x3C;0时，加上$2^n-1$即可实现向上舍入&lt;/p&gt;
&lt;h2&gt;9.float_abs&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;unsigned float_abs(unsigned uf) {
   unsigned abs = uf &amp;#x26; 0x7FFFFFFF;
   return abs &gt; 0x7F800000 ? uf : abs;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是要求一个浮点数的绝对值。符号位在最高位，0为正数，1为负数。当uf不为NAN时，直接返回abs即可。
当uf为NaN时，我们详细讨论一下：float有1位符号位，8位指数位，以及23位小数位。当指数位全为1且小数不为0时，uf为NaN。abs为uf除最高符号位的值，如果abs&gt;0x7F800000（即uf的指数位全为1且小数位不为0）返回uf。&lt;/p&gt;
&lt;h2&gt;10.float_i2f&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;unsigned float_i2f(int x) {
   unsigned uf, sign = 0, i, t;
   if (!x) return 0;
   if (x == -x) return 0xcf000000;
   sign = x &amp;#x3C; 0 ? 0x80000000 : 0;
   uf = x &amp;#x3C; 0 ? -x : x;

   for (i = 30; !(uf &amp;#x26; (1 &amp;#x3C;&amp;#x3C; i)); i--); 
   uf -= 1 &amp;#x3C;&amp;#x3C; i;

   if (i &amp;#x3C; 24) uf &amp;#x3C;&amp;#x3C;= 23 - i;
   else {
       t = i - 23;
       uf += 1 &amp;#x3C;&amp;#x3C; (t - 1); 
       uf -= (uf &amp;#x26; ((1 &amp;#x3C;&amp;#x3C; (t + 1)) - 1)) != 0;
       uf &gt;&gt;= t;
   }
   return uf + ((i + 127) &amp;#x3C;&amp;#x3C; 23) + sign;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;本题模拟int转到float的过程。关键在于确定小数位、指数位和符号位。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果x为0或者x为$T_{min}$，特殊情况特殊处理&lt;/li&gt;
&lt;li&gt;符号位：变量sign存储符号位&lt;/li&gt;
&lt;li&gt;uf变为x的绝对值（去除符号位的干扰）&lt;/li&gt;
&lt;li&gt;从高位扫描到低位，找到最高位的1的位置，并将最高位的1去除（减去1&amp;#x3C;&amp;#x3C;i），原因在于规格化值的小数部分已经隐含了1&lt;/li&gt;
&lt;li&gt;如果最高位的1在第23位或更低位，表明23位的小数部分可完全存下，无需舍入，只需要将uf的最高位1与最高位对齐即可。（如.01010-&gt;.10100)&lt;/li&gt;
&lt;li&gt;如果最高位的1在24位及更高位，需要进行舍入处理（偶舍入）。$t=i-23$是需要舍入的低位的位数。先加上1&amp;#x3C;&amp;#x3C;(t-1)即要被丢弃的数的一半先进行舍入操作。再由uf的低(t+1)位是否为0，如果不为0，表明1后面跟着0，不应该进位&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>C Lab实验报告</title><link>https://astro-pure.js.org/blog/csapp%E5%AE%9E%E9%AA%8C/c-lab%E5%AE%9E%E9%AA%8C%E6%8A%A5%E5%91%8A</link><guid isPermaLink="true">https://astro-pure.js.org/blog/csapp%E5%AE%9E%E9%AA%8C/c-lab%E5%AE%9E%E9%AA%8C%E6%8A%A5%E5%91%8A</guid><description>CSAPP C Lab实验解析</description><pubDate>Tue, 04 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;头文件queue.h&lt;/h2&gt;
&lt;p&gt;为了实现在时间复杂度O(1)内能够返回链表的大小和在尾部插入新节点，我们需要加上&lt;code&gt;q_size&lt;/code&gt;和&lt;code&gt;q_tail&lt;/code&gt;并实时维护。&lt;/p&gt;
&lt;h2&gt;源文件queue.c&lt;/h2&gt;
&lt;h3&gt;q_new&lt;/h3&gt;
&lt;p&gt;需要使用malloc开辟一个大小为&lt;code&gt;queue_t&lt;/code&gt;的内存空间并将类型为&lt;code&gt;queue_t&lt;/code&gt;的指针指向它，并将其&lt;code&gt;head&lt;/code&gt;、&lt;code&gt;tail&lt;/code&gt;设置为NULL&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;queue_t *q_new()
{
    /* Remember to handle the case if malloc returned NULL */
    queue_t *q = NULL;
    bool ok = (q = malloc(sizeof(queue_t)));
    if(ok)
    {
      q-&gt;head = NULL;
      q-&gt;tail = NULL;
      q-&gt;size = 0;
      return q;
    }
    else
      return NULL;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;q_free&lt;/h3&gt;
&lt;p&gt;首先判断传入&lt;code&gt;queue_t *q&lt;/code&gt;是否为nul,如果存在则从头节点遍历访问，删除每个节点&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;oid q_free(queue_t *q)
{
    /* Remember to free the queue structue and list elements */
    if(q)
    {
      list_ele_t *t= q-&gt;head;
      while(t)
      {
        list_ele_t *temp = t;
        t = t-&gt;next;
        free(temp);
      }
      free(q);
    }
    
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;q_insert_head&lt;/h3&gt;
&lt;p&gt;先新建一个节点并分配内存空间，在将新节点的&lt;code&gt;next&lt;/code&gt;指向原链表的&lt;code&gt;head&lt;/code&gt;节点，再将&lt;code&gt;head&lt;/code&gt;节点更新为新节点，对应的&lt;code&gt;size++&lt;/code&gt;。注意：如果原本q的大小为1，则新节点同时也是&lt;code&gt;tail&lt;/code&gt;节点&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;bool q_insert_head(queue_t *q, int v)
{
    /* What should you do if the q is NULL? */
    /* What if malloc returned NULL? */
    list_ele_t *newq;
    if(q)
    {
      bool ok = (newq = malloc(sizeof(list_ele_t)));
      if(ok)
      {
        newq-&gt;value = v;
        newq-&gt;next = q-&gt;head;
        q-&gt;head = newq;
        if(!q-&gt;size)
        {
          q-&gt;tail = newq;
        }
        q-&gt;size++;
        return true;
      }
      else return false;
    }
    return false;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;q_insert_tail&lt;/h3&gt;
&lt;p&gt;与&lt;code&gt;q_insert_head&lt;/code&gt;实现方法类似，唯一区别是要求在时间复杂度O(1)内实现插入，无法使用从头节点遍历的方法找到尾节点，可以直接使用维护的&lt;code&gt;tail&lt;/code&gt;节点。注意：如果原本q的大小为1,则新节点同时也是&lt;code&gt;head&lt;/code&gt;节点。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;bool q_insert_tail(queue_t *q, int v)
{
    /* Remember: It should operate in O(1) time */
    list_ele_t *newq;
    if(q)
    {
      bool ok = (newq = malloc(sizeof(list_ele_t)));
      if(ok)
      {
        newq-&gt;value = v;
        newq-&gt;next = NULL;
        if(!q-&gt;size)
        {
          q-&gt;tail = newq;
          q-&gt;head = newq;
          q-&gt;size++;
        }
        else{
          q-&gt;tail-&gt;next = newq;
          q-&gt;tail = newq;
          q-&gt;size++;
        }
        return true;
      }
      return false;
    }
    return false;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;q_remove_head&lt;/h3&gt;
&lt;p&gt;删除头节点，并在删除后将头节点设置为原头节点的下一节点。如果&lt;code&gt;q_size&lt;/code&gt;原本为1，则只有头节点，删除后应将头节点设置为null&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;bool q_remove_head(queue_t *q, int *vp)
{
    if(!q || !q-&gt;size)
      return false;
    else{
      if(vp)
      {
        *vp = q-&gt;head-&gt;value;
        list_ele_t *temp = q-&gt;head;
        q-&gt;head = temp-&gt;next;
        free(temp);
        --q-&gt;size;
        return true;
      }
    }
    return false;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;q_size&lt;/h3&gt;
&lt;p&gt;直接返回维护的&lt;code&gt;size&lt;/code&gt;即可&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;int q_size(queue_t *q)
{
    /* Remember: It should operate in O(1) time */
    if(!q || !q-&gt;size)
      return 0;
    else return q-&gt;size;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;q_reverse&lt;/h3&gt;
&lt;p&gt;翻转我们可以遍历两次实现，第一次将节点的值记录下来，第二次将节点的值反向更新，在不反转节点next节点的情况下即可实现反转操作&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;void q_reverse(queue_t *q)
{
  if(q &amp;#x26;&amp;#x26; q-&gt;size)
  {
      int value[q-&gt;size];
      list_ele_t *t = q-&gt;head;
      for(int i = q-&gt;size-1;i&gt;=0 &amp;#x26;&amp;#x26;t!=NULL;i--)
      {
        value[i] = t-&gt;value;
        t = t-&gt;next;
      }
      t = q-&gt;head;
      for(int i = 0;i&amp;#x3C;q-&gt;size &amp;#x26;&amp;#x26; t!=NULL;i++)
      {
        t-&gt;value = value[i];
        t = t-&gt;next;
      }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;010011 010011   q = 1/64
a = 1/4 +1/32 + 1/64 = 19/64
19/63&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>魔法三角形以及二项式系数的模2形式</title><link>https://astro-pure.js.org/blog/%E8%A1%A5%E9%A2%98%E8%AE%B0%E5%BD%95/%E9%AD%94%E6%B3%95%E4%B8%89%E8%A7%92%E5%BD%A2%E4%BB%A5%E5%8F%8A%E4%BA%8C%E9%A1%B9%E5%BC%8F%E7%B3%BB%E6%95%B0%E7%9A%84%E6%A8%A12%E5%BD%A2%E5%BC%8F</link><guid isPermaLink="true">https://astro-pure.js.org/blog/%E8%A1%A5%E9%A2%98%E8%AE%B0%E5%BD%95/%E9%AD%94%E6%B3%95%E4%B8%89%E8%A7%92%E5%BD%A2%E4%BB%A5%E5%8F%8A%E4%BA%8C%E9%A1%B9%E5%BC%8F%E7%B3%BB%E6%95%B0%E7%9A%84%E6%A8%A12%E5%BD%A2%E5%BC%8F</guid><description>魔法三角形以及二项式系数的模2形式</description><pubDate>Wed, 26 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;题目来源于CF2072F&lt;a href=&quot;https://codeforces.com/contest/2072/problem/F&quot;&gt;Problem - F - Codeforces&lt;/a&gt;
我们需要生成一个魔法三角形的第 nn 行。这个三角形的定义如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;第 i行有 i个整数。&lt;/li&gt;
&lt;li&gt;第一行只有一个整数 k。&lt;/li&gt;
&lt;li&gt;对于第 i 行的第 j个元素 $T_i,T_j$其值由以下规则决定：
&lt;ul&gt;
&lt;li&gt;如果$1&amp;#x3C;j&amp;#x3C;i$，则 $T_{i,j}=T_{i-1,j-1}$⊕$T_{i-1,j}$​（即上一行的第 j−1 和第 j 个元素的异或结果）。&lt;/li&gt;
&lt;li&gt;如果 j=1，则$T_{i,j}=T_{i-1,j}$（即上一行的第一个元素）。&lt;/li&gt;
&lt;li&gt;如果 j=i，则 $T_{i,j}=T_{i,j-1}$（即上一行的最后一个元素）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;当k = 3时：
        3 
       3 3 
      3 0 3 
     3 3 3 3 
     
    3 0 0 0 3 
   3 3 0 0 3 3 
  3 0 3 0 3 0 3 
 3 3 3 3 3 3 3 3 
 
3 0 0 0 0 0 0 0 3
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;方法一：递归&lt;/h1&gt;
&lt;p&gt;我们发现三角形在$2^k$ 行都全为k，并且是由边长为2的全为k的三角形以及对应边长的0的三角形组合而成。子结构相似，可以递归。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;void solve(){
	int n,k;
	cin&gt;&gt;n&gt;&gt;k;
	auto go = [&amp;#x26;](this auto &amp;#x26;&amp;#x26;self,unsigned t)-&gt;void{
		if(t==1) cout&amp;#x3C;&amp;#x3C;k&amp;#x3C;&amp;#x3C;&quot; &quot;;
		else if(t==2) cout&amp;#x3C;&amp;#x3C;k&amp;#x3C;&amp;#x3C;&quot; &quot;&amp;#x3C;&amp;#x3C;k&amp;#x3C;&amp;#x3C;&quot; &quot;;
		else
		{
			int w = bit_width(t);
			if(t == (1&amp;#x3C;&amp;#x3C;(w-1)))
			{
				for(int i =1;i&amp;#x3C;=t;i++)
					cout&amp;#x3C;&amp;#x3C;k&amp;#x3C;&amp;#x3C;&quot; &quot;;
			}
			else
			{
				unsigned x = t - (1&amp;#x3C;&amp;#x3C;(w-1));
				self(x);
				for(int i = 1;i&amp;#x3C;=t-2*x;i++)
					cout&amp;#x3C;&amp;#x3C;0&amp;#x3C;&amp;#x3C;&quot; &quot;;
				self(x);
			}
		}
	};
	go(n);
	cout&amp;#x3C;&amp;#x3C;endl;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;方法二：二项式系数的模 2 形式&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;异或操作的性质使得魔法三角形的每一行实际上是一个 &lt;strong&gt;二项式系数的模 2 形式&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;具体来说，第 n 行的第 j 个元素的值取决于 $\tbinom{n-1}{j-1}mod2$
二项式系数$\tbinom{n-1}{j-1}mod2$ 可以通过&lt;strong&gt;位运算&lt;/strong&gt;来计算
当且仅当$(n-1)&amp;#x26;(j-1)==(j-1)$ 时成立&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$\tbinom{n}{k}$逐位比较 n 和 k 的二进制表示：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果 k的某一位为 1，而 n的对应位为 0，则模2为0&lt;/li&gt;
&lt;li&gt;否则，模2为1&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;//jiangly
void solve() {
    int n, k;
    cin &gt;&gt; n &gt;&gt; k;
    for (int i = 0; i &amp;#x3C; n; i++) {
        cout &amp;#x3C;&amp;#x3C; (((n - 1) &amp;#x26; i) == i ? k : 0) &amp;#x3C;&amp;#x3C; &quot; \n&quot;[i == n - 1];
    }
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>牛客周赛Round80补题</title><link>https://astro-pure.js.org/blog/%E8%A1%A5%E9%A2%98%E8%AE%B0%E5%BD%95/%E7%89%9B%E5%AE%A2%E5%91%A8%E8%B5%9Bround80</link><guid isPermaLink="true">https://astro-pure.js.org/blog/%E8%A1%A5%E9%A2%98%E8%AE%B0%E5%BD%95/%E7%89%9B%E5%AE%A2%E5%91%A8%E8%B5%9Bround80</guid><description>牛客周赛Round80补题</description><pubDate>Tue, 11 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;D题[&lt;a href=&quot;https://ac.nowcoder.com/acm/contest/101196/D&quot;&gt;D-举手赢棋hard_牛客周赛 Round 80&lt;/a&gt;]()&lt;/h1&gt;
&lt;h2&gt;题意&lt;/h2&gt;
&lt;p&gt;字符串s，0表示失利，1表示胜利，任意时刻要满足胜场大于等于负场，有两次机会可以强行获得比赛胜利，恰好两次举手，求方案数&lt;/p&gt;
&lt;h2&gt;代码&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;	int n;
	ll mn = 1e9;
	string s;
	cin&gt;&gt;n&gt;&gt;s;
	s = &quot; &quot;+s;
	ll a[n+1],c0[n+1];
	memset(a,0,sizeof a);
	memset(c0,0,sizeof c0);
	for(int i =1;i&amp;#x3C;=n;i++)
	{
		a[i] = a[i-1];
		if(s[i]==&apos;1&apos;) a[i]++;
		else a[i]--;
		c0[i] = c0[i-1] + (s[i]==&apos;0&apos;);
		mn = min(mn,a[i]);
	}
	if(mn&amp;#x3C;-4)
	{
		cout&amp;#x3C;&amp;#x3C;0&amp;#x3C;&amp;#x3C;endl;
		return 0;
	}
	int pos1 = -1;
	for(int i =1;i&amp;#x3C;=n;i++)
	{
		if(a[i]&amp;#x3C;0)
		{
			pos1 = i;
			break;
		}
	}
	if(pos1==-1)
	{
		cout&amp;#x3C;&amp;#x3C;n*(n-1)/2&amp;#x3C;&amp;#x3C;endl;
		return 0;
	}
	int pos2 = -1;
	for(int i =pos1;i&amp;#x3C;=n;i++)
	{
		a[i]+=2;
		if(a[i]&amp;#x3C;0 &amp;#x26;&amp;#x26; pos2==-1)
		{
			pos2 = i;
		}
	}
	if(pos2==-1)
	{
		ll ans = 0;
		for(int i =1;i&amp;#x3C;=pos1;i++)
		{
			if(s[i]==&apos;0&apos;)
			{
				ans+=(n-i) + i-c0[i];
			}
		}
		cout&amp;#x3C;&amp;#x3C;ans&amp;#x3C;&amp;#x3C;endl;
		return 0;
	}
	ll ans = 0;
	for(int i = 1;i&amp;#x3C;=pos1;i++)
	{
		if(s[i]==&apos;0&apos;)
		{
			ans+= (c0[pos2]-c0[i]);
		}
	}
	cout&amp;#x3C;&amp;#x3C;ans&amp;#x3C;&amp;#x3C;endl;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;思路&lt;/h2&gt;
&lt;p&gt;计算10字串1-0的个数差$a[i]$，以及$c[i]$表示i左侧（包含）的0的个数
1.$min(a[i])&amp;#x3C;-4$ 即使举手两次也无法合法
2.如果所有的前缀和均大于等于0，可以任意选2个
3.$pos1$ 为第一个$a[i]&amp;#x3C;0$ 的下标，一定至少要在、$[1,pos1]$ 中间选一个0,$[pos1,n] a[i]$ 加上2,其后所有位置均合法，第二个位置任意选择
4.pos1操作后仍然不合法,pos2为pos1后$a[pos1]&amp;#x3C;0$ ，$[1,pos1]$ 选1个0，第二个位置也必须要选择一个0并且要选pos2之前的0。第一个0在i处，则第二个0在$[i+1,pos2]$ 区间内&lt;/p&gt;
&lt;h1&gt;E题&lt;/h1&gt;
&lt;h2&gt;坑点&lt;/h2&gt;
&lt;p&gt;注意一个黑子可能可以吃掉多个白色连通块，此时ans应该累加而不是取max
能用bfs就用bfs，dfs传参复杂容易出错&lt;/p&gt;
&lt;h2&gt;代码&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;int n;
cin&gt;&gt;n;
for(int i = 1;i&amp;#x3C;=n;i++)
	for(int j = 1;j&amp;#x3C;=n;j++)
	{
		cin&gt;&gt;mp[i][j];
	}
	for(int i =1;i&amp;#x3C;=n;i++)
		for(int j = 1;j&amp;#x3C;=n;j++)
		{
			if(vis[i][j]) continue;
			if(mp[i][j]==&apos;.&apos;||mp[i][j]==&apos;#&apos;) continue;
			queue&amp;#x3C;pair&amp;#x3C;ll,ll&gt;&gt; q;
			q.emplace(i,j);
			set&amp;#x3C;pair&amp;#x3C;ll,ll&gt;&gt; st;
			int cnt = 0;
			while(q.size())
			{
				auto [x,y] = q.front();
				q.pop();
				if(vis[x][y]) continue;
				vis[x][y] = true;
				cnt++;
				for(int k =0;k&amp;#x3C;4;k++)
				{
					int xx = x + dx[k],yy = y + dy[k];
					if(xx&amp;#x3C;=0 || xx&gt;n || yy&amp;#x3C;=0 || yy&gt;n || mp[xx][yy]==&apos;#&apos;)
					{
						continue;
					}
					if(mp[xx][yy]==&apos;.&apos;)
					{
						st.emplace(xx,yy);
						continue;
					}
					q.emplace(xx,yy);
				}
			}
			if(st.size()==1)
			{
				auto [x,y] = *st.begin();
				ans[x][y] += cnt;
			}

		}
	ll res = 0;
	for(int i =1;i&amp;#x3C;=n;i++)
		for(int j = 1;j&amp;#x3C;=n;j++)
		{
			res = max(res,ans[i][j]);
		}
	cout&amp;#x3C;&amp;#x3C;res&amp;#x3C;&amp;#x3C;endl;
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;F题&lt;/h1&gt;
&lt;h2&gt;题意&lt;/h2&gt;
&lt;p&gt;在长为$2n$的数组中两两配对，要求配对绝对值之和为k，求组合方式&lt;/p&gt;
&lt;h2&gt;思路&lt;/h2&gt;
&lt;p&gt;将数组分为两组，一组和为A，一组和为B，有A+B = $2n(1+2n)/2=n(1+2n)$
又有B-A=k ，联立有$2B=n(1+2n)+k=2n^2+n+k,B=n^2+(n+k)/2$
则n和k一定同奇偶，可以排除一种情况
接下来将k分解为奇数，因为当匹配的一对数是不同奇偶例如1,4时中间长度为偶数，可以两两匹配，间隔为1。&lt;/p&gt;
&lt;h2&gt;代码&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;void solve(){
	int n,k;
	cin&gt;&gt;n&gt;&gt;k;
	if(k&amp;#x3C;n || k &gt; n * n || (n%2)!=k%2)
	{
		cout&amp;#x3C;&amp;#x3C;-1&amp;#x3C;&amp;#x3C;endl;
		return ;
	}
	int s = 2 * n -1;
	vector&amp;#x3C;int&gt; v;
	for(int i = 1;i&amp;#x3C;=n;i++)//分解奇数
	{
		while(s+(n-i)&gt;k)
			s-=2;
		v.push_back(s);
		k -= s;
		if(s &gt; 2)
		{
			s-=2;
		}
	}
	vector&amp;#x3C;int&gt; a;
	vector&amp;#x3C;int&gt; b;
	vector&amp;#x3C;int&gt; vis(2*n+1);//记录
	int now = 2 * n;
	for(auto &amp;#x26;x : v)
	{
		while(vis[now]) now--;
		a.emplace_back(now);
		b.emplace_back(now-x);
		vis[now] = 1;
		vis[now-x] =1;
	}
	for(int i=0;i&amp;#x3C;n;i++)
	{
		cout&amp;#x3C;&amp;#x3C;a[i]&amp;#x3C;&amp;#x3C;&quot; &quot;&amp;#x3C;&amp;#x3C;b[i]&amp;#x3C;&amp;#x3C;endl;
	}

}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;G题[&lt;a href=&quot;https://ac.nowcoder.com/acm/contest/101196/G&quot;&gt;G-不公平对局_牛客周赛 Round 80&lt;/a&gt;]()&lt;/h1&gt;
&lt;h2&gt;题目大意&lt;/h2&gt;
&lt;p&gt;小红和小紫正在对弈。在围棋规则中，每吃掉对方的一枚棋子，就需要将这枚棋子放入棋盖中。然而，棋盖空间不大，她们任何一方吃子数量达到 x 就输了。&lt;br&gt;
当然，我们不需要考虑具体的对弈局面，模型简化如下，每个回合将会依次执行以下两步：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;小红有$p_1$的概率吃掉对方一枚棋子；&lt;/li&gt;
&lt;li&gt;小紫有$p_2$的概率吃掉对方一枚棋子。&lt;br&gt;
谁吃子数量达到 x 就输了。小红执黑先手，她想知道自己最终获胜的概率是多少？你需要将答案对 $10^9+7$取模后输出。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;思路&lt;/h2&gt;
&lt;p&gt;概率dp+逆元
$dp[i,j]$表示小红落子i个，小紫落子j个
递推式$dp[i,j]=(p1&lt;em&gt;p2&lt;/em&gt;dp[i+1][j+1]+p1*(1-p2)&lt;em&gt;dp[i+1][j]+(1-p1)&lt;em&gt;p2&lt;/em&gt;dp[i][j+1])/(1-(1-p1)&lt;/em&gt;(1-p2)$&lt;/p&gt;
&lt;h2&gt;代码&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;int x,a1,b1,a2,b2;
	cin&gt;&gt;x&gt;&gt;a1&gt;&gt;b1&gt;&gt;a2&gt;&gt;b2;
	int p1 = a1 * ksm(b1,mod-2)%mod;
	int p2 = a2 * ksm(b2,mod-2)%mod;
	int P1 = p1 * p2 %mod;
	int P2 = p1 * (mod+1-p2)%mod;
	int P3 = (mod+1-p1) * p2 % mod;
	int P4 = (mod+1-p1) * (mod+1-p2)%mod;
	int P5 = (mod+1 -P4) %mod;
	int inv = ksm(P5,mod-2);
	int dp[1010][1010];
	for(int i =0;i&amp;#x3C;=x-1;i++)
	{
		dp[x][i] = 0;
		dp[i][x] = 1;
	}
	
	for(int i = x-1;i&gt;=0;i--)
		for(int j = x-1;j&gt;=0;j--)
		{
			dp[i][j] = inv * ((P1*dp[i+1][j+1]%mod + P2 * dp[i+1][j]%mod)%mod+P3*dp[i][j+1]%mod)%mod;
            //cout&amp;#x3C;&amp;#x3C;dp[i][j]&amp;#x3C;&amp;#x3C;endl;
		}
	cout&amp;#x3C;&amp;#x3C;(dp[0][0]+mod)%mod&amp;#x3C;&amp;#x3C;endl;
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>ABC389总结补题</title><link>https://astro-pure.js.org/blog/%E8%A1%A5%E9%A2%98%E8%AE%B0%E5%BD%95/abc389%E6%80%BB%E7%BB%93%E8%A1%A5%E9%A2%98</link><guid isPermaLink="true">https://astro-pure.js.org/blog/%E8%A1%A5%E9%A2%98%E8%AE%B0%E5%BD%95/abc389%E6%80%BB%E7%BB%93%E8%A1%A5%E9%A2%98</guid><description>ABC389总结补题</description><pubDate>Tue, 21 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;C-Snake Queue&lt;/h1&gt;
&lt;h2&gt;题目大意&lt;/h2&gt;
&lt;p&gt;有一个蛇队列。最初，队列是空的。&lt;/p&gt;
&lt;p&gt;您将获得 Q个查询，应按给出的顺序进行处理。查询有三种类型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;类型 1 ：以 &lt;code&gt;1 l&lt;/code&gt; 的形式给出。长度为 l的蛇被添加到队列末尾。如果队列在添加之前是空的，则新添加的蛇的头部位置为 0 ；否则，它是队列中最后一条蛇的头部坐标和最后一条蛇的长度之和。&lt;/li&gt;
&lt;li&gt;类型 2 ：以 &lt;code&gt;2&lt;/code&gt; 的形式给出。队列前面的蛇离开队列。保证队列此时不为空。令 mm 为离开的蛇的长度，则队列中剩余的每个蛇的头部坐标减少 m 。&lt;/li&gt;
&lt;li&gt;类型 3 ：以 &lt;code&gt;3 k&lt;/code&gt; 形式给出。输出从队列前面开始第 k 条蛇的头部坐标。保证此时队列中至少有 k 条蛇&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;思路&lt;/h2&gt;
&lt;p&gt;比赛时第一想法是把蛇的队列在类型二时一个一个修改仍在队列中的值，但时间复杂度过高。第二想法是记录已经出队的蛇的长度，坐标减去已经出队的蛇的长度就是当前的坐标。&lt;strong&gt;题目保证了让队列前面的蛇离开队列过程中保证不为空，则队列为空添加时就是第一次，无需将已经出队蛇的长度归零。即每条新进入队列的蛇的坐标保持递增！&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;代码&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;

struct snake
{
	long long h;
	long long m;
};
deque&amp;#x3C;snake&gt; snakes;

int main(){
	int q;
	cin&gt;&gt;q;
	long long outs = 0;

	while(q--){
		int t;
		cin&gt;&gt;t;
		if(t==1)
		{
			long long l;
			cin&gt;&gt;l;
			if(snakes.size()){
				long long x = snakes.back().h;
				long long y = snakes.back().m;
				snakes.push_back({x+y,l});
			}
			else snakes.push_back({0,l});
		}
		else if(t==2)
		{
			long long m = snakes.front().m;
			outs-=m;
			snakes.pop_front();
		}
		else if(t==3)
		{
			long long k;
			cin&gt;&gt;k;
			cout&amp;#x3C;&amp;#x3C;snakes[k-1].h+outs&amp;#x3C;&amp;#x3C;endl;
		}
	}
	return 0;
}


&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;D-Squares in Circle&lt;/h1&gt;
&lt;h2&gt;题目大意&lt;/h2&gt;
&lt;p&gt;On the two-dimensional coordinate plane, there is an infinite tiling of 1×11×1 squares.
Consider drawing a circle of radius R centered at the center of one of these squares. How many of these squares are completely contained inside the circle?
More precisely, find the number of integer pairs (i,j) such that all four points (i+0.5,j+0.5)(i+0.5,j+0.5), (i+0.5,j−0.5)(i+0.5,j−0.5), (i−0.5,j+0.5)(i−0.5,j+0.5), and (i−0.5,j−0.5)(i−0.5,j−0.5) are at a distance of at most R from the origin.&lt;/p&gt;
&lt;h2&gt;思路&lt;/h2&gt;
&lt;p&gt;考试时做出来的，但在小数上犯了大车。&lt;strong&gt;技巧：可以同时乘以4，让括号内的0.5变为1,避免出现小数&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;代码&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;
typedef long long ll;

int main() {
    ll r;
    cin &gt;&gt; r;
    ll m = (r-1)*4+1;
    ll n = 0;

    for(ll  i = 3;i&amp;#x3C;2*r;i+=2){
        n+=(ll)(sqrt(4*r*r - i*i)-1)/2;
    }
    cout&amp;#x3C;&amp;#x3C;m+4*n&amp;#x3C;&amp;#x3C;endl;
    return 0;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;E-Square Price&lt;/h1&gt;
&lt;h2&gt;题目大意&lt;/h2&gt;
&lt;p&gt;有 N种产品，每种产品有 $10^{100}$ 件库存。
您可以购买任意非负数量的每种产品。要购买第 i个产品的 k 件，需要花费 $k^2P_i$ 日元。
如果您的总购买成本最多为 M日元，您总共可以购买的最大单位数是多少？&lt;/p&gt;
&lt;h3&gt;约束&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;$1 \leq N \leq 2 \times 10^{5}$&lt;/li&gt;
&lt;li&gt;$1 \leq M \leq 10^{18}$&lt;/li&gt;
&lt;li&gt;$1 \leq P_i \leq 2 \times 10^{9}$&lt;/li&gt;
&lt;li&gt;所有输入值均为整数。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;思路&lt;/h2&gt;
&lt;p&gt;看到M非常大，枚举时间复杂度太高，注意到每件产品的花费为$P_i,3P_i,5P_i……$ 即每件产品是单调递增的，可以考虑用二分法。设每次最多买价格为X的商品。&lt;strong&gt;注意是能够全部买完X，则右移，如果只能买部分的X，需要左移。最后再用剩下的钱看能否再买部分的价格为X+1的商品。&lt;/strong&gt;
&lt;strong&gt;特别注意溢出，数字太大，最好都开大一些&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;代码&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;
typedef long long ll;
#define int long long

ll ans;
ll n,m;
const int N = 2e6+10;
vector&amp;#x3C;ll&gt; p(N);
bool check(int x)
{
	__int128 ans = 0;
	for(int i = 1;i&amp;#x3C;=n;i++)
	{
		int temp = (x/p[i]+1)/2;
		ans+=(__int128)p[i]*temp*temp;
		if(ans&gt;m) return false;
	}
	return true;
}
signed main(){
	std::ios::sync_with_stdio(false);
	std::cin.tie(0);
	cin&gt;&gt;n&gt;&gt;m;
	for(int i = 1;i&amp;#x3C;=n;i++)
	{
		cin&gt;&gt;p[i];
	}
	int l = 0;
	int r = m;
	int ans= -1;

	while(l &amp;#x3C;= r)
	{
		ll mid = (l+r)/2;
		if(check(mid))
		{
			ans = mid;//一定可以全部购买&amp;#x3C;=mid的，更新答案
			l = mid+1;
		}
		else r = mid-1;
	}
	int res = 0;
	for(int i = 1;i&amp;#x3C;=n;i++)
	{
		int temp = (ans/p[i]+1)/2;
		res+=temp;
		m-=temp*temp*p[i];
	}
	res+=m/(ans+1);
	cout&amp;#x3C;&amp;#x3C;res&amp;#x3C;&amp;#x3C;endl;
	return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;F-Rated Range&lt;/h1&gt;
&lt;h2&gt;题目大意&lt;/h2&gt;
&lt;p&gt;Takahashi 计划参加 N场 AtCoder 比赛。&lt;/p&gt;
&lt;p&gt;在第 i 场比赛( 1≤i≤N)中，如果他的评分在 Li和 Ri之间(含)，他的评分将增加 1。&lt;/p&gt;
&lt;p&gt;您将获得以下格式的 Q个查询：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;给出一个整数 X 。假设 Takahashi 的初始评分为 X ，请在参加所有 N 场比赛后确定他的评分。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;思路&lt;/h2&gt;
&lt;p&gt;后续重做&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>牛客小白月赛109补题</title><link>https://astro-pure.js.org/blog/%E8%A1%A5%E9%A2%98%E8%AE%B0%E5%BD%95/%E7%89%9B%E5%AE%A2%E5%B0%8F%E7%99%BD%E6%9C%88%E8%B5%9B109%E8%A1%A5%E9%A2%98</link><guid isPermaLink="true">https://astro-pure.js.org/blog/%E8%A1%A5%E9%A2%98%E8%AE%B0%E5%BD%95/%E7%89%9B%E5%AE%A2%E5%B0%8F%E7%99%BD%E6%9C%88%E8%B5%9B109%E8%A1%A5%E9%A2%98</guid><description>牛客小白月赛109补题</description><pubDate>Mon, 20 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;C猪猪养成计划1&lt;/h1&gt;
&lt;h2&gt;题目描述&lt;/h2&gt;
&lt;p&gt;1.从l-&gt;r，与没有玩耍过的猪猪玩耍，标记是第几个玩耍的猪，并标记已玩耍
2.对编号为x的猪猪输出是第几个和Tobo玩耍的猪，没有玩耍过则为0&lt;/p&gt;
&lt;h2&gt;思路&lt;/h2&gt;
&lt;p&gt;考场思路：建两个数组，一个存储是第几个玩耍的猪，一个用于标记是否玩耍过
但n,q都是最大10的五次方，操作时l,r会重复多次扫描已经标记过的猪，时间复杂度太高
解法一：想到要去掉已经扫描过的猪，让下次不会再扫描。可以用set维护，已经玩耍过后从set中删去&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;

int tot;
set&amp;#x3C;int&gt; st;
int b[200010];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, q;
    cin &gt;&gt; n &gt;&gt; q;
    for(int i = 1;i&amp;#x3C;=n;i++)
        st.insert(i);
    while(q--){
        int op;
        cin&gt;&gt;op;
        if(op == 1){
            int l,r;
            cin&gt;&gt;l&gt;&gt;r;
            auto it = st.lowr_bound(l);
            while(it ! =st.end() &amp;#x26;&amp;#x26; *it &amp;#x3C;= r){
		        b[*it] = ++tot;
		        it = st.erase(it);
            }
        }
        else{
            int x;
            cin&gt;&gt;x;
            cout&amp;#x3C;&amp;#x3C;b[x]&amp;#x3C;&amp;#x3C;endl;
        }
    }
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;D猪猪养成计划2&lt;/h1&gt;
&lt;h2&gt;题目描述&lt;/h2&gt;
&lt;p&gt;Tobo 养了 n 只猪猪，但是 Tobo 为了不让它们吃醋，所以一视同仁不管陪哪只猪猪都只陪 m 天。&lt;br&gt;
现在第i只猪猪有个要求，要 Tobo 第 $a_i$​ 天去陪它，即需要从第$a_i$天陪到第 $a_{i+m-1}$ 天。然而，因为 Tobo 精力有限，一天只能陪一只猪猪，显然 Tobo 大概率不能陪伴他的每只猪猪。&lt;br&gt;
如果不陪第 iii 只猪猪，就要给它买 $val_i$价值的礼物来补偿它；反之，如果陪它，就要花费$b_i$ 的精力。&lt;br&gt;
现在 Tobo 需要做一个抉择来选择陪的猪猪来使得花费的金钱与精力之和最少。他想知道这个最小值是多少。&lt;/p&gt;
&lt;h2&gt;思路&lt;/h2&gt;
&lt;p&gt;如果陪0只猪，那么总的金钱和精力和为$\sum_{1}^{n}val_i$ ,如果陪第i只猪那么金钱和精力和为$\sum_{1}^{n}val_i-val_i+b_i$ 可以看到我们要求b-val的最小值，可以用dp求&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;

int main(){
	std::ios::sync_with_stdio(false);
	std::cin.tie(0);
	int n,m;
    cin&gt;&gt;n&gt;&gt;m;
	long long a[100010][3];
    vector&amp;#x3C;vector&amp;#x3C;long long&gt;&gt; c(n+10);
    long long res = 0;
    for(int i = 1;i&amp;#x3C;=n;i++) cin&gt;&gt;a[i][0];
    for(int i = 1;i&amp;#x3C;=n;i++) cin&gt;&gt;a[i][1]&gt;&gt;a[i][2],res+=a[i][2];
    for(int i = 1;i&amp;#x3C;=n;i++) c[a[i][0]].push_back(a[i][1] - a[i][2]);//插入b-val
    long long dp[n+10];
    memset(dp,0x7f,sizeof dp);//注意不能用1e18赋值，1e18为浮点数
    dp[0] = 0;//按照天数动态规划，第0天为0
    //定义：第i天的最小的b-val
    for(int i = 0;i&amp;#x3C;=n;i++)
    {
        long long ci = 0x7f;
        for(auto x : c[i]) dp[i+m] = min(dp[i+m],dp[i]+x);
        dp[i+1] = min(dp[i+1],dp[i]);
    }
    cout&amp;#x3C;&amp;#x3C;res+dp[n+1]&amp;#x3C;&amp;#x3C;endl;
	return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;&lt;a href=&quot;%5BE-min25%E7%AD%9B_%E7%89%9B%E5%AE%A2%E5%B0%8F%E7%99%BD%E6%9C%88%E8%B5%9B109%5D(https://ac.nowcoder.com/acm/contest/99785/E)&quot;&gt;Emin25筛&lt;/a&gt;&lt;/h1&gt;
&lt;h2&gt;思路&lt;/h2&gt;
&lt;p&gt;可以看到如果是暴力的话时间复杂度是$O(n^2)$，太慢！观察到$5&lt;em&gt;5=25$ ，可以转化为模5的奇数个和偶数个,可以存储前缀积,$a_i&lt;/em&gt;a_{i+1}**……*a_j = sum_j/sum_{i-1}$
可以从后往前枚举左端点，用$num_0$和$num1$分别表示左端点右边5因子奇偶的和，按照对应的公式计算即可。
&lt;strong&gt;注意除法容易造成精度问题，可以用除法逆元,$b^{-1}=b^{(p-2)} mod p$&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;代码&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;int n;
	cin&gt;&gt;n;
	vector&amp;#x3C;int&gt; a(n+1),cnt(n+1);
	vector&amp;#x3C;long long&gt; sum(n+1),num(2,0);
	vector&amp;#x3C;long long&gt; pre(n+1);
	sum[0] = 1;
	for(int i = 1;i&amp;#x3C;=n;i++)
	{
		cin&gt;&gt;a[i];
		while(a[i]%5==0){
			a[i]/=5;
			cnt[i]++;
		}
		cnt[i]%=2;
	}
	for(int i = 1;i&amp;#x3C;=n;i++){
		sum[i] = sum[i-1]*a[i];
		pre[i] = (pre[i-1]+cnt[i])%2;
	}

	long long ans = 0;
	for(int i = n;i&gt;=0;i--)
	{
        ans = (ans + num[pre[i]] * ksm(sum[i],mod-2) % mod) % mod;
        ans = (ans + num[pre[i] ^ 1] *ksm(sum[i],mod-2) %mod *  5 % mod) % mod;
        num[pre[i]] += sum[i];
        num[pre[i]] %= mod;
	}
	cout&amp;#x3C;&amp;#x3C;ans&amp;#x3C;&amp;#x3C;endl;
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>CF Round996(Div2)补题</title><link>https://astro-pure.js.org/blog/%E8%A1%A5%E9%A2%98%E8%AE%B0%E5%BD%95/cf-round996div2</link><guid isPermaLink="true">https://astro-pure.js.org/blog/%E8%A1%A5%E9%A2%98%E8%AE%B0%E5%BD%95/cf-round996div2</guid><description>CF Round996(Div2)补题</description><pubDate>Tue, 14 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;B.Crafting&lt;/h1&gt;
&lt;h2&gt;思路&lt;/h2&gt;
&lt;p&gt;对于需要增加的材料，最多只能有一个，不能有两个及以上，因为如果有两个及以上的话，所有需要增加材料的和是不变的，不可能最终每个材料增加满足b(i)
对于有一个需要增加的材料：依次枚举验证其他的材料减少完之后是否会小于b(i)即可&lt;/p&gt;
&lt;h1&gt;C.The Trail&lt;/h1&gt;
&lt;h2&gt;题目大意&lt;/h2&gt;
&lt;p&gt;一个$n*m$ 的格子中有一条路径从(1,1)到(n,m)，其路径上的格子中的数未知，其他格子中都给定有数字，题目要求所有的行、列数字之和均相等，求路径上未知的数字&lt;/p&gt;
&lt;h2&gt;思路&lt;/h2&gt;
&lt;p&gt;设$a_{ij}$为第i行第j列的数字，$rowsum=columnsum$,$n&lt;em&gt;rowsum=m&lt;/em&gt;columnsum$
有两种情况&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$n \neq m$ 时：此时$rowsum=columnsum=0$&lt;/li&gt;
&lt;li&gt;$n=m$ 时：此时$rowsum=columnsum=任意值$
为了方便讨论，直接令所有情况全为0
&lt;strong&gt;注意到路径仅能往下或者往右走，那么总有某行或者某列只有路径上的一个点未知&lt;/strong&gt;
可以用这个性质递推&lt;/li&gt;
&lt;li&gt;当D即向下走时，所在行只有路径上的点未知&lt;/li&gt;
&lt;li&gt;当R即向右走时，所在列只有路径上的点未知&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;代码&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;void solve(){
	int n,m;
	cin&gt;&gt;n&gt;&gt;m;
	string s;
	cin&gt;&gt;s;
	vector&amp;#x3C;vector&amp;#x3C;long long&gt;&gt; a;
	for(int i = 0;i &amp;#x3C; n;i++){//输入
		a.push_back(vector&amp;#x3C;long long&gt;(m));
		for(int j = 0;j&amp;#x3C;m;j++){
			cin&gt;&gt;a[i][j];
		}
	}
	//模拟路径
	int x = 0,y = 0;//当前的坐标位置
	for(char c : s){
		if(c==&apos;D&apos;){
			long long su = 0;
			for(int i = 0;i&amp;#x3C;m;i++){
				su += a[x][i];
			}
			a[x][y] = -su;//赋相反值
			++x;
		}
		else{
			long long su = 0;
			for(int i = 0;i&amp;#x3C;n;i++){
				su += a[i][y];
			}
			a[x][y] = -su;//赋相反值
			++y;
		}
	}
	long long su = 0;
	for(int i = 0;i&amp;#x3C; m;i++){
		su += a[n-1][i];//终点
	}
	a[n-1][m-1] = -su;//终点处赋值
	for(int i = 0;i&amp;#x3C; n;i++){
		for(int j = 0;j&amp;#x3C;m;j++){
			cout&amp;#x3C;&amp;#x3C;a[i][j]&amp;#x3C;&amp;#x3C;&quot; &quot;;
			
		}
		cout&amp;#x3C;&amp;#x3C;endl;
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;D.Scarecrow&lt;/h1&gt;
&lt;h2&gt;题目大意&lt;/h2&gt;
&lt;p&gt;在一维数轴上有一些稻草人(&gt;0)，乌鸦初始在x=0处，乌鸦会一直检测左侧是否有稻草人，如果左侧稻草人距离自己小于k（左侧最近稻草人的坐标为y），那么它立马会向右移动到y+k个，稻草人每秒移动1个单位长度，左右或者停止都可以。问乌鸦要到达l至少需要的时间的两倍。&lt;/p&gt;
&lt;h2&gt;思路&lt;/h2&gt;
&lt;p&gt;乌鸦可以被距离k的稻草人推着走，也可以被小于k的稻草人跳跃着走。易得跳跃时花费的时间更少，所以要最大化跳跃次数。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;case1:只有左侧有稻草人时，只与左侧最近的稻草人有关，被左侧的稻草人推着走&lt;/li&gt;
&lt;li&gt;case2:只有右侧有稻草人时，需要右侧最近的稻草人移动到乌鸦处，让乌鸦开始跳跃起来&lt;/li&gt;
&lt;li&gt;case3:左右都有稻草人时，左右的稻草人应该同时向乌鸦处运动，左侧离乌鸦最近的稻草人一定距离k（刚刚跳跃过来），&lt;strong&gt;左侧稻草人推着乌鸦右移，右侧稻草人左移接近乌鸦，到达乌鸦处实现下一次跳跃&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;代码&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;void solve(){
	 int n;
	 int k,l;
	 cin&gt;&gt;n&gt;&gt;k&gt;&gt;l;
	 k *= 2;
	 l *= 2;

	 vector&amp;#x3C;int&gt; a(n);
	 for(int i = 0;i&amp;#x3C;n;i++ ) cin&gt;&gt;a[i];//输入
	 for(int i = 0;i&amp;#x3C;n;i++ ) a[i]*=2;

	int ans = a[0];//初始a[0]移到原点时间
	int x = k;//x是当前乌鸦的位置
	for(int i = 1;i&amp;#x3C; n;i++){//开始枚举第二到第n个的稻草人
		if(a[i]&gt;x){//如果在乌鸦右侧
			a[i] = max(x,a[i]-ans);//稻草人当前位置，最左时停在乌鸦当前处
			ans += (a[i]-x)/2;//左侧乌鸦会一直推着走，相当于相向而行
			x = (x + a[i])/2 + k;//最终走到乌鸦出发点x和下一个稻草人处中点进行跳跃
		}
		else{//下一个稻草人在左侧
			a[i] = min(x,a[i]+ans);//a[i]朝稻草人动
			x = a[i] + k;//下一个稻草人一定会在上一个稻草人的右方，一定和稻草人的间距小于k
		}
	}

	if(x &amp;#x3C; l){//若通过跳跃无法到达l处，则只能用稻草人推着乌鸦走
		ans += l - x;
	}
	cout&amp;#x3C;&amp;#x3C;ans&amp;#x3C;&amp;#x3C;endl;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>牛客周赛Round76总结补题</title><link>https://astro-pure.js.org/blog/%E8%A1%A5%E9%A2%98%E8%AE%B0%E5%BD%95/%E7%89%9B%E5%AE%A2%E5%91%A8%E8%B5%9Bround76%E6%80%BB%E7%BB%93%E8%A1%A5%E9%A2%98</link><guid isPermaLink="true">https://astro-pure.js.org/blog/%E8%A1%A5%E9%A2%98%E8%AE%B0%E5%BD%95/%E7%89%9B%E5%AE%A2%E5%91%A8%E8%B5%9Bround76%E6%80%BB%E7%BB%93%E8%A1%A5%E9%A2%98</guid><description>牛客周赛Round76总结补题</description><pubDate>Mon, 13 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;C 小红的gcd&lt;/h1&gt;
&lt;h2&gt;题目描述&lt;/h2&gt;
&lt;p&gt;小红有一个长度为$n$的数组，她希望数组元素之和越少越好。&lt;br&gt;
她可以进行&lt;strong&gt;任意次&lt;/strong&gt;操作，每次选择数组中的两个元素$a_i$和 $a_j$ ，令 $a_i = a_j = gcd(a_i,a_j)$ 。  所有操作结束后，请你输出&lt;strong&gt;最小&lt;/strong&gt;的数组元素之和。&lt;/p&gt;
&lt;h2&gt;思路&lt;/h2&gt;
&lt;p&gt;找到数组所有元素共有的gcd，对数组元素依次对总gcd取gcd即可，最后就是数组的总和就是$ans=n*gcd$&lt;/p&gt;
&lt;h1&gt;D 奇偶调整(题假了)&lt;/h1&gt;
&lt;h2&gt;题目描述&lt;/h2&gt;
&lt;p&gt;UPD：&lt;strong&gt;本题假了&lt;/strong&gt;，hack数据如下：&lt;br&gt;
2 2 1
11 9&lt;br&gt;
正确答案应该为 13 ，而不是 14 。&lt;/p&gt;
&lt;p&gt;小红有一个长度为$n$ 的数组 $a$ ，第 $i$ 个元素为 $a_i$​ 。
小红可以执行两种操作，操作内容如下：&lt;br&gt;
1.选择一个偶数元素 $a_i$ ，令 $a_i=a_i/2$。&lt;br&gt;
2.选择一个奇数元素 $a_i$，令 $a_i=a_i⊕1$ 。&lt;br&gt;
其中，操作 111 最多执行 mmm 次，操作 222 最多执行 kkk 次。小红想最小化数组元素之和，请你输出这个值。&lt;br&gt;
⊕ 代表按位异或。&lt;/p&gt;
&lt;h2&gt;思路&lt;/h2&gt;
&lt;h3&gt;补充&lt;/h3&gt;
&lt;p&gt;对$a_i$与1取位异或，奇数取完相当于减一，偶数取完加一&lt;/p&gt;
&lt;h3&gt;贪心解答（假了）&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;

int main(){
	int n;
	cin&gt;&gt;n;
	int m,k;
	cin&gt;&gt;m&gt;&gt;k;
	priority_queue&amp;#x3C;long long&gt; q;
	for(int i = 1;i&amp;#x3C;=n;i++){
		int x;
		cin&gt;&gt;x;
		q.push(x);
	}
	long long ans = 0;
	while(!q.empty()){
		auto u = q.top();
		q.pop();
		if(!u)continue;
		if(u&amp;#x26;1){
			if(k&gt;=1){
				u--;
				k--;
				q.push(u);
			}
			else{
				ans += u;
			}
		}
		else if(u % 2 == 0){
			if(m){
				u /= 2;
				m--;
				q.push(u);
			}
			else{
				ans += u;
			}
		}
	}
	cout&amp;#x3C;&amp;#x3C;ans&amp;#x3C;&amp;#x3C;endl;
	return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;关键在&lt;strong&gt;优先队列(priority_queue)，顶部是最大的数&lt;/strong&gt;，最开始还在傻傻地写vector,然后每次操作完成之后排序。。。。&lt;strong&gt;注意大小，开Long long&lt;/strong&gt;&lt;/p&gt;
&lt;h1&gt;E 幂次进近&lt;a href=&quot;https://ac.nowcoder.com/acm/contest/99990/E&quot;&gt;幂次进近&lt;/a&gt;&lt;/h1&gt;
&lt;h2&gt;题目描述&lt;/h2&gt;
&lt;p&gt;给定 t 次询问，每次询问给出两个正整数 n 和 k 。&lt;br&gt;
请你找到最小的&lt;strong&gt;正整数&lt;/strong&gt; m ，使得 $n-m^k$ 的&lt;strong&gt;绝对值&lt;/strong&gt;最小。&lt;/p&gt;
&lt;h2&gt;思路&lt;/h2&gt;
&lt;p&gt;比赛时得了180(200)，有两个点没有过。注意需要用__int128_（$2^{127}-1 约等于{10}^{38}$），二分法逼近m,使用快速幂求$m^k$ ，$2^{60}≈1.1529×10^{18}$&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;//__int128_读入写入模板
void read(__int128 &amp;#x26;x) {
    string s;
    cin &gt;&gt; s;
    x = 0;
    for (char c : s) {
        x = x * 10 + (c - &apos;0&apos;);
    }
}

void print(__int128 x) {
    if (x &amp;#x3C; 0) {
        cout &amp;#x3C;&amp;#x3C; &apos;-&apos;;
        x = -x;
    }
    if (x &gt; 9) print(x / 10);
    cout &amp;#x3C;&amp;#x3C; (char)(x % 10 + &apos;0&apos;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;

void read(__int128 &amp;#x26;x) {
    string s;
    cin &gt;&gt; s;
    x = 0;
    for (char c : s) {
        x = x * 10 + (c - &apos;0&apos;);
    }
}

void print(__int128 x) {
    if (x &amp;#x3C; 0) {
        cout &amp;#x3C;&amp;#x3C; &apos;-&apos;;
        x = -x;
    }
    if (x &gt; 9) print(x / 10);
    cout &amp;#x3C;&amp;#x3C; (char)(x % 10 + &apos;0&apos;);
}

__int128_t calc(__int128_t m,__int128_t k){
	__int128_t ans = 1;
	while(k &gt; 0){
		if(k &amp;#x26; 1){
			ans *= m;
		}
		m *= m;
		k&gt;&gt;=1;
	}
	return ans;
}

__int128_t solve(__int128_t n,__int128_t k){
	if(k==1) return n;
	if(k&gt;60) return 1;
	__int128_t r = pow(n,1.0/k)+1;
	__int128_t l = 1;
	__int128_t bestm = 1;
	__int128_t mindiff = n -1;
	while(l &amp;#x3C;= r){
		__int128_t mid = (r - l)/2 + l;
		__int128_t minpow = calc((r - l)/2 + l ,k);

		if(minpow &amp;#x3C;=n){
			__int128_t diff = n - minpow;
			if(diff &amp;#x3C; mindiff || (diff == mindiff &amp;#x26;&amp;#x26; mid &amp;#x3C; bestm)){
				mindiff = diff;
				bestm = mid;
			}
			l = mid + 1;
		}
		else{
			__int128_t diff = minpow - n;
			if(diff &amp;#x3C; mindiff || (diff == mindiff &amp;#x26;&amp;#x26; mid &amp;#x3C; bestm)){
				mindiff = diff;
				bestm = mid;
			}
			r = mid - 1;
		}

	}
	return bestm;

}

int main(){
	std::ios::sync_with_stdio(false);
	std::cin.tie(0);
	int t;
	cin&gt;&gt;t;
	while(t--){
		__int128_t n,k;
		read(n);
		read(k);
		print(solve(n,k));
		cout&amp;#x3C;&amp;#x3C;endl;
	}

	return 0;
}


&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;F同位序列&lt;/h1&gt;
&lt;h2&gt;题目描述&lt;/h2&gt;
&lt;p&gt;定义 f(x) 为 x 在二进制表示下的 1 的个数。&lt;br&gt;
例如 f(7)=3，f(8)=1，f(9)=2。&lt;br&gt;
定义 g(x) 为 第一个比 x 大的数字 y ，使得 f(x)=f(y)。
例如 g(1)=2，g(2)=4，g(3)=5。
给你一个长度为 n 的数组 a ，第 i 个元素为 $a_i$。&lt;br&gt;
我们希望从数组 a中挑选出一些元素，构造一个长度为 m 的同位序列 b 。&lt;br&gt;
同位序列 满足如下条件：&lt;br&gt;
对于所有的$j∈[2,m]$，都有$b_j=g(b_{j-1})$ 。&lt;br&gt;
当然，同位序列越长越好，请你最大化其长度 m ，然后输出 m 和对应的同位序列。  如果有多解，输出任意一个即可。&lt;/p&gt;
&lt;h2&gt;思路&lt;/h2&gt;
&lt;p&gt;题目要求从给定的一个数组中，找到最大的同为序列长度m,即求m的最大值
有点像LIS，可以想到dp做法
我们先看如何确定g(x)
例如: 14的二进制为$1110$，$g(14)=19$,19的二进制为$00010011$,我们可以观察到，将第一次连续出现的1中的第一个1向高位移一位，剩余连续的1移到最低位，与&lt;code&gt;next_permutation&lt;/code&gt;算法即可&lt;/p&gt;
&lt;p&gt;状态转移：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果当前元素x的g(x)在x之前的数组当中，那么就接在其序列&lt;/li&gt;
&lt;li&gt;如果不在，就要新建一个序列&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;代码&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;#x3C;bits/stdc++.h&gt;
using namespace std;

void solve(){
    const int M = 32;
    int n;
    cin&gt;&gt;n;
    map&amp;#x3C;int,int&gt; g;//g[i]
    vector&amp;#x3C;int&gt; a(n);
    for(auto &amp;#x26;x : a){//求g(i)
        cin&gt;&gt;x;
        vector&amp;#x3C;int&gt; bit(M);//x拆为二进制
        for(int j = 0;j&amp;#x3C;M;j++) if(x&gt;&gt;j &amp;#x26; 1) bit[M-j-1]=1;//化x为二进制
        next_permutation(bit.begin(),bit.end());
        for(int j = 0;j&amp;#x3C;M;j++) if(bit[M-j-1]) g[x]|= 1 &amp;#x3C;&amp;#x3C; j;
    }
    set&amp;#x3C;int&gt; ans;
    sort(a.rbegin(),a.rend());//从大到小
    map&amp;#x3C;int,int&gt; mp;//相当于dp
    int max = 0,id = 0;

    for(auto x : a){
        if(mp.contains(g[x])) mp[x] = mp[g[x]] + 1;//如果以前序列中已经出现g[x],长度+1
        else mp[x] = 1;//新建一个序列
        if(mp[x] &gt; max) max = mp[x],id = x;  //id是最长序列的首元素 
    }

    ans.clear();
    ans.insert(id);
    while(mp.contains(g[id])){
        id = g[id];
        ans.insert(id);
    }

    cout&amp;#x3C;&amp;#x3C;ans.size()&amp;#x3C;&amp;#x3C;endl;
    for(auto x : ans) cout&amp;#x3C;&amp;#x3C;x&amp;#x3C;&amp;#x3C;&quot; &quot;;
}
int main() {
    solve();
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>C++高程期末总结</title><link>https://astro-pure.js.org/blog/c%E6%9C%9F%E6%9C%AB%E5%A4%8D%E4%B9%A0/c%E6%9C%9F%E6%9C%AB%E5%A4%8D%E4%B9%A0</link><guid isPermaLink="true">https://astro-pure.js.org/blog/c%E6%9C%9F%E6%9C%AB%E5%A4%8D%E4%B9%A0/c%E6%9C%9F%E6%9C%AB%E5%A4%8D%E4%B9%A0</guid><description>WHU大一上C++高程期末复习总结</description><pubDate>Thu, 09 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;第二章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;volatile&lt;/code&gt; 易变的     当要求使用volatile声明变量值的时候，系统总是重新从它所在的内存读取数据，即使它前面的指令刚刚从该处读取过数据，编译器将不会优化该变量的代码&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;register&lt;/code&gt; 寄存器变量  就是编译程序时希望把该变量放到寄存器中，这样可以提高对该变量的访问速度。由前言可知，CPU访问寄存器的速度是高于访问内存的速度；&lt;strong&gt;不需要经常修改但常常访问，cpu能够接受的类型，小于等于整型长度，局部静态变量无法定义为寄存器变量，不能定义任意多个计算器变量。cpu寄存器数量有限！！！&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;static&lt;/code&gt; 静态关键字    存在全局存储区中，仅在变量的作用范围内可见。生命周期变长。__修饰全局变量时，改变了全局变量的作用域，限制了在当前文件中，无法被其他文件extern---&gt;（内部链接）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;extern&lt;/code&gt; 外来的关键字  声明外部变量， 具有外部链接的对象，被说明的对象必须是&lt;strong&gt;静态生存期的变量&lt;/strong&gt;（全局变量或静态变量）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;**全局变量默认extern类别，局部变量默认局部动态生存期。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;thread_local&lt;/code&gt; 具有线程存储生存期，是 Thread 的局部变量，用于编多线程程序。只能用于**修饰命名空间以及块作用域中的变量，或者已经被指定为 static 的变量。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;mutable&lt;/code&gt; ：只能用于&lt;strong&gt;类数据成员&lt;/strong&gt;，并且&lt;strong&gt;不能与 const 或者 static 同时使用，不能用来修饰引用变量&lt;/strong&gt;。 mutable关键字是为了突破 const 关键字的限制 被 mutable 关键字修饰的成员变量永远处于可变的状态，**即使是在被const 修饰的成员函数中。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;constexpr&lt;/code&gt;修饰变量和&lt;code&gt;const&lt;/code&gt;修饰变量的区别在于：初始化const变量的&lt;strong&gt;可以不是&lt;/strong&gt;一个constexpr，初始化constexpr的**必须是constexpr&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;基本运算符优先级
高
单目运算符
算术运算符（先乘除，后加减）
移位运算符
关系运算符
逻辑运算符（！除外）
条件运算符
赋值运算符
逗号运算符
低&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./1.png&quot; alt=&quot;1&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;++x返回的是增加后的数据，而x++返回的是增加前的数据，--同理&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;逻辑运算符的短路原则&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;不同数据类型的转换&lt;/h3&gt;
&lt;h4&gt;短-&gt;长&lt;/h4&gt;
&lt;p&gt;无符号数，高位补0；有符号数，高位补符号位&lt;/p&gt;
&lt;h4&gt;长-&gt;短&lt;/h4&gt;
&lt;p&gt;截取低位赋值，丢弃高位&lt;/p&gt;
&lt;h3&gt;数据类型显示转换&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;int(z)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(int)z&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;类型转换操作符&amp;#x3C;类型说明符&gt;(表达式)&lt;/li&gt;
&lt;li&gt;类型操作转换符有:&lt;code&gt;const_cast,dynamic_cast,reinterpret_cast,static_cast``如static_cast&amp;#x3C;int&gt;(z)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;简单IO格式控制&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./2.png&quot; alt=&quot;2&quot;&gt;&lt;/p&gt;
&lt;h3&gt;简单控制语句&lt;/h3&gt;
&lt;h5&gt;switch&lt;/h5&gt;
&lt;p&gt;break；跳出case分支的跳转语句，不用break；会依次执行其他case，最终跳出switch语句&lt;/p&gt;
&lt;h4&gt;typedef声明&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;./3.png&quot; alt=&quot;3&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;typedef int INTEGER&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;typedef int Array[10];&lt;/code&gt;数组要放在后面，因为[]是在类型名的类型名的后面&lt;/li&gt;
&lt;li&gt;&lt;code&gt;typedef int* IntPtr;&lt;/code&gt;原始定义:int* a;-&gt;int* IntPtr;-&gt;typedef int* IntPtr;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;typedef int (*FuncPtr)(int,int);&lt;/code&gt;原型:&lt;code&gt;int (*funcPtr)(int,int)&lt;/code&gt;,前面为返回值，后面括号内为参数的类型&lt;/li&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;  { int month;
  int day;
  int year;
  }d;
typedef struct date{
  int month;
  int day;
  int year;
}DATE;```
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;typedef-----C++11新用法&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;using 别名  = 类型名;&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;//例如:
using SI = Sales_item; //SI是别名
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;//当typedef用类型别名指代复合类型或常量时，把类型别名用到声明语句中会产生意想不到的结果。
typedef char* pstring;//pstring是一个指向char类型的指针
const pstring cstr = 0;//注意，const char* cstr = 0;是错误的，并不是指向一个const char的指针
//正确：cstr是一个指向char类型变量的常量指针
const pstring *ps;//ps是一个指向常量const类型指针的指针
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;auto和decltype&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;auto类型由编译器分析表达式的类型&lt;/li&gt;
&lt;li&gt;当我们定义一个变量与某一表达式类型相同，但是不想用该表达式初始化该变量时使用。&lt;/li&gt;
&lt;li&gt;例：decltype (i)j = 2;//根据i的数据类型确定j的数据类型&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;第三章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;被调函数定义的位置放在主调函数之后，则必须在函数调用之前使用被调函数的原型说明；一定要声明之后编译器才知道后面出现了这个函数&lt;/li&gt;
&lt;li&gt;void addrat(int,int); void addrat(int a,int b);两种函数原型均可&lt;/li&gt;
&lt;li&gt;rand()返回一个伪随机数;void srand(unsigned int seed)为 rand 函数设置一个起始点，从而影响 rand 函数生成的随机数序列。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;函数的参数传递&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;函数调用时才分配形参储存单元&lt;/li&gt;
&lt;li&gt;实参类型必须与形参相符&lt;/li&gt;
&lt;li&gt;值传递是单向传递，无法修改实值；引用传递是双向传递&lt;/li&gt;
&lt;li&gt;常引用作参数可以保障实参数据的安全&lt;/li&gt;
&lt;li&gt;传递指针可以间接实现双向传递（值传递的特例）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;指针形参的具体实现方式：在函数中创建一个新的指针变量，并将形参指针的值（即内存地址）赋值给新的指针变量，这两个变量实际指向同一块内存空间。在函数内部的操作全是对这个新的指针变量，如果改变所指对象的值，则对象的值改变；若改变指针值（即改变内存地址），则不影响实参指针&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;引用相当于对标识符取别名，声明一个引用时，&lt;strong&gt;必须同时对它进行初始化，指向一个已经存在的对象&lt;/strong&gt;，相当于一个常量指针（&lt;strong&gt;Constant Pointer&lt;/strong&gt;），不可以改变指向，但可以改变指向对象的值，同时引用并没有内存空间，因为并没有创建变量
&lt;ul&gt;
&lt;li&gt;实现双向传递通常使用引用传递，形参与实参共用一个内存空间&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;内联函数&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;声明时使用关键字inline&lt;/li&gt;
&lt;li&gt;编译系统将程序中出现内联函数调用的地方用函数体进行替换。&lt;/li&gt;
&lt;li&gt;内联函数中&lt;strong&gt;不可以出现循环结构和switch语句&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;不加inline关键字的短小、频繁调用的函数也可能被编译器自动认为是内联函数；同样，加了inline关键字的函数也不一定就是内联函数（太过复杂就不是）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;是否内联由编译器去决定&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;constexpr函数&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;能用于常量表达式的函数&lt;/li&gt;
&lt;li&gt;&lt;code&gt;constexpr&lt;/code&gt; 函数的函数体必须非常简单，通常只能包含一条 &lt;code&gt;return&lt;/code&gt; 语句（C++14 放宽了限制）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;constexpr&lt;/code&gt; 函数&lt;/strong&gt;用于在编译时计算函数结果，适用于常量表达式和性能优化。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;带默认形参的函数&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;有默认参数的形参必须放在形参列表最后&lt;/strong&gt;，否则会造成歧义&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./4.png&quot; alt=&quot;4&quot;&gt;&lt;/p&gt;
&lt;h3&gt;函数重载&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;形参类型不同或者形参个数不同&lt;/li&gt;
&lt;li&gt;注意如果没有完全匹配的重载函数，编译器会进行实参的数据类型隐式转换，以找到最佳匹配的函数，&lt;strong&gt;但此时可能会出现二义性&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;函数重载和默认参数同时使用，有可能引起二义性```
void DrawCircle(int r=0,int x=0, int y=0);
void DrawCircle(int r);&lt;/li&gt;
&lt;li&gt;函数返回类型不同不能作为重载条件&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;第四章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;只有在类实例化时，才能给类中的数据成员初始化&lt;/li&gt;
&lt;li&gt;访问权限可以任意顺序出现，但每个成员只能有一种访问属性&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public&lt;/code&gt;任何外部函数都可以访问&lt;/li&gt;
&lt;li&gt;&lt;code&gt;private&lt;/code&gt;只允许本类中的函数及友元访问&lt;/li&gt;
&lt;li&gt;&lt;code&gt;protected&lt;/code&gt;只能被本类成员函数，派生类成员函数和友元访问。(&lt;strong&gt;与&lt;code&gt;private&lt;/code&gt;差别在于继承过程中派生类能够访问)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;类的成员函数&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;可以在类内声明，类外添加类名作用域实现；也可直接在类内给出函数体，形成内联成员函数&lt;/li&gt;
&lt;li&gt;允许声明重载函数和带默认形参值的函数&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;在类中定义的函数成员自动成为内联函数&lt;/strong&gt;，在类外定义需要添加inline关键字（&lt;strong&gt;声明不写，定义时写inline&lt;/strong&gt;）&lt;/li&gt;
&lt;li&gt;不能在函数原型处说明inline，否则编译器会处理为普通函数&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;构造函数&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;如果没有声明构造函数，编译器会自动生成默认构造函数，参数列表为空&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;构造函数可以是内联函数、重载函数、带默认参数值的函数&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;构造函数函数名与类名相同，且不指定返回值类型&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;构造函数可以进行重载操作&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果显式定义了构造函数，则编译器不再提供默认构造函数&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;**无形参的构造函数称为默认构造函数，全部参数都有默认形参值的构造函数也是默认构造函数&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;**成员初始化顺序与它们在类定义中的出现顺序一致，而不是在初始值中出现的顺序!早声明的先执行初始化&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;const成员或引用成员必须将其初始化！&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;委托构造函数：使用它所属类的其他构造函数执行自己的初始化过程&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;拷贝构造函数：用一个已经存在的本类对象初始化新对象&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;类名 (const 类名&amp;#x26; 对象名);//声明原型
类名::类 (const 类名&amp;#x26; 对象名){
}//实现
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;拷贝构造函数被调用的三种情况：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;定义新对象时以另一个对象为初始值&lt;/li&gt;
&lt;li&gt;函数的形参是类的对象，将使用实参对象初始化形参对象&lt;/li&gt;
&lt;li&gt;如果函数的返回值是类的对象，函数执行完成返回主调函数时，将使用return语句中的对象初始化一个临时无名对象，传递给主调函数，此时发生复制构造。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;析构函数&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;析构函数是类的一个公有成员函数，可以写在类内，也可以写在类外&lt;/li&gt;
&lt;li&gt;析构函数不能有参数，&lt;strong&gt;不能重载&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;移动构造函数(不太可能考)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;对持久存在变量的引用称为左值引用，用&amp;#x26;表示（即第3章引用类型）,对短暂存在可被移动的右值的引用称之为右值引用，用&amp;#x26;&amp;#x26;表示&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;通过标准库&lt;code&gt;&amp;#x3C;utility&gt;&lt;/code&gt;中的&lt;code&gt;move&lt;/code&gt;函数可将左值对象移动为右值&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;float n = 6;
float &amp;#x26;lr_n = n; //左值引用
float &amp;#x26;&amp;#x26;rr_n = n; //错误，右值引用不能绑定到左值
float &amp;#x26;&amp;#x26;rr_n = n * n; //右值表达式绑定到右值引用
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通过标准库中的move函数可将左值对象移动为右值
float n = 10;
float &amp;#x26;&amp;#x26;rr_n = std::move(n); //将n转化为右值&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;
#include&amp;#x3C;utility&gt;
class astring {
public :
std::string s;
astring (astring&amp;#x26;&amp;#x26; o) noexcept: s(std::move(o.s)) //显式移动所有成员
{ 函数体 }

}//不分配新内存，被移动的对象不应再使用，避免多余复制操作，除非销毁或者重新赋值
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;default 、delete 函数&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;=default:要求编译器生成默认或拷贝构造函数&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;=delete:要求编译器删除指定函数，除了析构函数之外都可以删除&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;Mystr() = default;//默认无参构造函数
Mystr(Mystr &amp;#x26;str) = default;//默认拷贝构造函数
Mystr(Mystr &amp;#x26;str) = delete;//删除拷贝构造函数 
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;前向引用&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;前向引用并没有定义类，无法在提供完整定义类之前声明该对象，也不能再内联成员函数中使用该类，因为编译器不知道这个类的结构是怎样的，只知道存在这个类，不能涉及类的细节&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;仅仅进行前向声明而没有定义的类是不完整的，这样的类，只能用于**定义指针、引用、以及用于函数形参的指针和引用。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;结构体&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;结构体与类几乎完全相同，仅默认访问权限不同。class默认访问权限是private,而struct默认访问权限是public&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;联合体union&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;成员共用相同的内存单元&lt;/li&gt;
&lt;li&gt;任何两个成员不会同时有效，只会取其一&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;枚举union&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;不限定作用域的枚举类型和限定作用域的枚举类型&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;不能对枚举元素赋值，但可以声明时初始化&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;枚举元素有默认值，依次为0,1,2,3,.....&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;枚举元素的作用域是全局的，会造成命名空间的污染&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;枚举变量的值可以在运行时修改，但只能赋值为枚举类型中定义的枚举元素。枚举变量只能存储枚举类型中的枚举元素，不能直接赋值为整数（除非进行强制类型转换）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;枚举类（限定作用域枚举）枚举类的枚举元素位于枚举类的作用域内，不会污染全局命名空间。- 枚举类的枚举元素不能隐式转换为整数，必须显式转换。可以显式指定枚举类的底层类型（如 &lt;code&gt;int&lt;/code&gt;、&lt;code&gt;char&lt;/code&gt; 等）。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;//enum 枚举类型名{枚举元素值(变量值) 列表};
enum Weekday{
  sun,mon,tue,wed,thu,fri,sat
};
//限定作用域枚举类
enum class Weekday {
  SUN, MON, TUE
};
Weekday today = Weekday::SUN;  // 必须使用作用域运算符 ::
int day = static_cast&amp;#x3C;int&gt;(Weekday::SUN);  // 显式转换为整数
enum class Weekday : char {
  SUN, MON, TUE
};
enum class Color { RED, GREEN };
enum class TrafficLight { RED, YELLOW, GREEN };
Color c = Color::RED;  // 不会与 TrafficLight::RED 冲突
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;第五章&lt;/h2&gt;
&lt;h3&gt;标识符的作用域和可见性&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;函数原型的作用域在函数内&lt;/li&gt;
&lt;li&gt;局部作用域&lt;/li&gt;
&lt;li&gt;类作用域，包含类内和非内联成员函数的函数体。如果在类作用域以外访问类的成员，要通过&lt;strong&gt;类名（访问静态成员），或者该类的对象名、对象引用、对象指针（访问非静态成员）&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;文件作用域：如全局变量&lt;/li&gt;
&lt;li&gt;命名空间作用域：程序可以有多个命名空间，可以在不同命名空间中使用相同的命名而不混淆&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;生存期&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;全局变量，静态局部变量（局部可见性）拥有静态生存期&lt;/li&gt;
&lt;li&gt;块作用域声明的，没有用static修饰的对象是动态生存期对象（局部生存期对象），不指定初值时，初值不确定&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;类的静态成员&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;分为静态数据成员和静态成员函数&lt;/li&gt;
&lt;li&gt;不使用静态成员时：所有的类都各自有数据成员，共用一份成员函数(使用this指针来分辨哪个对象调用的)&lt;/li&gt;
&lt;li&gt;静态数据成员
&lt;ul&gt;
&lt;li&gt;static关键字声明&lt;/li&gt;
&lt;li&gt;所有该类的对象共享&lt;/li&gt;
&lt;li&gt;必须在类外定义和初始化,用::指明作用域&lt;/li&gt;
&lt;li&gt;可以是三种权限之一&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;静态成员函数
&lt;ul&gt;
&lt;li&gt;类外使用类名和作用域操作符来调用&lt;/li&gt;
&lt;li&gt;**只能引用该类的静态数据成员或者静态成员函数&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;类的友元&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;友元函数和友元类&lt;/li&gt;
&lt;li&gt;友元函数能够通过对象名访问private和protect成员&lt;/li&gt;
&lt;li&gt;有两种形式的友元函数
&lt;ul&gt;
&lt;li&gt;一个不属于任何类的函数声明为当前类的友元，称为当前类的友元函数&lt;/li&gt;
&lt;li&gt;一个其他类的成员函数声明为当前类的友元，称为当前类的友元成员&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;友元函数声明可以放在public、private、protected的任意段中&lt;/li&gt;
&lt;li&gt;友元类：若一个类为另一个类的友元，则此类的所有成员函数都能访问对方类的&lt;strong&gt;私有成员&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;友元关系不具有传递性，且是单向的&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;共享数据的保护&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;对于需要共享但不需要改变的数据应声明为const类型，不改变对象状态的成员函数声明为常函数&lt;/li&gt;
&lt;li&gt;const对象必须初始化&lt;/li&gt;
&lt;li&gt;常成员函数&lt;code&gt;类型说明符 函数名（参数表）const;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;**通过常对象只能调用它的常成员函数&lt;/li&gt;
&lt;li&gt;对于常数据成员，只能通过构造函数初始化列表方式初始化数据&lt;/li&gt;
&lt;li&gt;**常成员函数可以访问常对象中的数据成员，但仍然不允许修改常对象中数据成员的值（可访问变量但是只读）&lt;/li&gt;
&lt;li&gt;**特例mutable:被mutable修饰的变量，将永远处于可变的状态，即使在一个const函数中&lt;code&gt;mutalbe double len;//len可以永远被修改&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;第六章&lt;/h2&gt;
&lt;h3&gt;数组&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;数组名是一个地址常量，储存了数组首元素的内存的地址&lt;/li&gt;
&lt;li&gt;数组地址是连续的&lt;/li&gt;
&lt;li&gt;数组名作形参，对形参数组的改变会直接影响到实参数组。退化为了指针变量。&lt;/li&gt;
&lt;li&gt;静态数组（前面加static关键字）只初始化一次，对于大型数组可提高运行效率&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;指针&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;直接访问：按变量名直接存取变量值；间接访问：通过存放变量地址的变量去访问变量&lt;/li&gt;
&lt;li&gt;空类型指针&lt;code&gt;void*&lt;/code&gt;，表示不确定指向哪一种数据类型，使用时需要强制类型转换&lt;code&gt;p1 = (char *)p2;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;任何⼀个指针本身的数据值都是unsigned long int型。&lt;/li&gt;
&lt;li&gt;**指针常量(constant Pointer)，指针本身的值不变&lt;/li&gt;
&lt;li&gt;**常量指针（Pointer to constant)，指向常量数据类型&lt;/li&gt;
&lt;li&gt;指针具有算术运算和关系运算&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;输入整行字符串&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;getline(cin,s)&lt;/code&gt;输入到s中&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getline(cin,s2,&apos;,&apos;&lt;/code&gt;第三个参数可以设置分隔符作为字符串结束的标志&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;指针数组&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;由指针类型元素组成的数组&lt;/li&gt;
&lt;li&gt;指针作形参本质仍然是值传递&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./5.png&quot; alt=&quot;5&quot;&gt;&lt;/p&gt;
&lt;h3&gt;指针型函数&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;函数的返回值是地址&lt;/li&gt;
&lt;li&gt;**不要将非静态局部地址用作函数的返回值，返回的指针要确保在主调函数中是有效、合法的地址。例如不要将局部定义的变量传回主调函数，此时局部变量已经不存在了&lt;/li&gt;
&lt;li&gt;&lt;code&gt;int* function(){}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;指向函数的指针&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;返回值类型 (*functionPointer)(参数类型);&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;void (*functionPointer)(float);//指向函数的指针
functionPointer = printStuff;//printStuff是已有函数
functionPointer(PI);//函数指针调用
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;对象指针&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;类名 *对象指针名&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Point *ptr;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;this指针&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;隐含于每一个类的成员函数中的特殊指针&lt;/li&gt;
&lt;li&gt;指出成员函数当前操作的数据所属的对象&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;指向类的非静态成员的指针&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;类型说明符 类名::*类成员指针名;&lt;/code&gt;声明指向类内数据成员的指针&lt;/li&gt;
&lt;li&gt;&lt;code&gt;类成员指针名 = &amp;#x26;类名::数据成员名;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;访问:&lt;code&gt;对象名.*类成员指针名&lt;/code&gt;    &lt;code&gt;对象指针名-&gt;*类成员指针名&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;指向函数：&lt;code&gt;类型说明符 (类名::*类成员指针名)(参数表);&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;赋值&lt;code&gt;指针名=&amp;#x26;类名::函数成员名;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;访问函数成员  &lt;code&gt;(对象名.*类成员指针名)(参数表)&lt;/code&gt;   &lt;code&gt;(对象指针名-&gt;*类成员指针名)(参数表)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;指向类的静态成员函数&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;不用依赖于具体的对象调用，直接用&lt;strong&gt;普通指针&lt;/strong&gt;实现&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;new delete&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;new 数据类型（初始化参数列表）&lt;/li&gt;
&lt;li&gt;delete 指针名&lt;/li&gt;
&lt;li&gt;new 类型名T [ 数组长度 ]&lt;/li&gt;
&lt;li&gt;**delete[] 数组名p&lt;/li&gt;
&lt;li&gt;&lt;code&gt;new 类型名T[第1维长度][第2维长度]....&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;vector&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;表示对数组元素的引用，vector数组对象名不表示数组的首地址&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;浅拷贝、深拷贝&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;浅拷贝：仅复制指针的地址，导致指向同一块内存空间&lt;/li&gt;
&lt;li&gt;深拷贝：重新新建一块内存空间&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;第七章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;**派生类不能继承基类的构造函数和析构函数&lt;/li&gt;
&lt;li&gt;如果派生类声明了一个和某基类成员同名的新成员（如果是成员函数，则参数表也要相同，参数不同的情况属于重载），**派生的新成员就覆盖了外层同名成员&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;访问控制&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;不同继承方式的影响：1.派生类成员对基类成员的访问权限 2.通过派生类对象对基类成员的访问控制&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;public继承&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;基类的public和protected成员的访问属性在派生类中保持不变，但基类的private成员不可直接访问。&lt;/li&gt;
&lt;li&gt;派生类中的成员函数可以直接访问基类中的public和protected成员，但不能直接访问基类的private成员。&lt;/li&gt;
&lt;li&gt;通过派生类的对象只能访问基类的public成员&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;protected继承&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;基类的public和protected成员都以protected身份出现在派生类中，但基类的private成员不可直接访问&lt;/li&gt;
&lt;li&gt;派生类中的成员函数可以直接访问基类中的public和protected成员，但不能直接访问基类的private成员。&lt;/li&gt;
&lt;li&gt;通过派生类的对象不能直接访问基类中的任何成员&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;private继承&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;基类的public和protected成员都以private身份出现在派生类中，但基类的private成员不可直接访问。&lt;/li&gt;
&lt;li&gt;派生类中的成员函数可以直接访问基类中的public和protected成员，但不能直接访问基类的private成员。&lt;/li&gt;
&lt;li&gt;通过派生类的对象不能直接访问基类中的任何成员。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;类型兼容原则&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;一个公有派生类对象在使用上可以被作为基类的对象
&lt;ul&gt;
&lt;li&gt;可以隐含转换为基类对象&lt;/li&gt;
&lt;li&gt;派生类对象可以初始化基类的引用&lt;/li&gt;
&lt;li&gt;派生类的指针可以隐含转换基类的指针&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;通过基类对象名、指针只能使用从基类继承的成员&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;派生类的构造和析构函数&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;只需要对本类中新增成员进行初始化，继承而来的基类对象可以自动调用基类构造函数完成&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;派生类的构造函数需要给基类的构造函数传参&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;//单继承
派生类名::派生类名(基类所需的形参，本类成员所需的形参):基类名(参数表), 本类成员初始化列表

{

//其他初始化；

}；
//多继承
派生类名::派生类名(参数表):基类名1(基类1初始化参数

表), 基类名2(基类2初始化参数表), ...基类名n(基类n初始化

参数表), 本类成员初始化列表

{

//其他初始化；

}；

C::C(int i,int j): B(i), c(j){

cout &amp;#x3C;&amp;#x3C; &quot;C&apos;s constructor called.&quot; &amp;#x3C;&amp;#x3C; endl;

}//C是派生类，B是基类，传递i给B的构造函数,c是新增变量
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;构造、析构函数的调用顺序&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;先调用基类的构造函数，按照**继承时声明的顺序&lt;/li&gt;
&lt;li&gt;本类成员初始化，按照在**类中的声明顺序&lt;/li&gt;
&lt;li&gt;析构函数与构造函数调用顺序相反&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;作用域分辨符&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;基类和派生类有相同成员时，通过派生类对象调用的是派生类中的同名成员&lt;/li&gt;
&lt;li&gt;如果要通过派生类对象访问基类同名成员，需要加上作用域&lt;/li&gt;
&lt;li&gt;二义性问题可以通过限定类名解决，也可以虚基类、虚函数解决&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;虚基类&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;解决多继承（菱形继承）时可能发生的对同一基类继承多次而产生的二义性问题.&lt;/li&gt;
&lt;li&gt;虚基类的成员是由最派生类的构造函数通过调用虚基类的构造函数进行初始化的。&lt;/li&gt;
&lt;li&gt;在整个继承结构中，直接或间接继承虚基类的所有派生类，都必须在构造函数的成员初始化表中给出对虚基类的构造函数的调用。如果未列出，则表示调用该虚基类的默认构造函数。&lt;/li&gt;
&lt;li&gt;在建立对象时，只有最派生类的构造函数调用虚基类的构造函数，该派生类的其他基类对虚基类构造函数的调用被忽略。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;
#include &amp;#x3C;iostream&gt;
using namespace std;
// 虚基类
class A {
public:
    int data;
    A(int value) : data(value) {
        cout &amp;#x3C;&amp;#x3C; &quot;A constructed with value: &quot; &amp;#x3C;&amp;#x3C; value &amp;#x3C;&amp;#x3C; endl;
    }
};
// 中间类 B，虚继承 A
class B : virtual public A {
public:
    B(int value) : A(value) {
        cout &amp;#x3C;&amp;#x3C; &quot;B constructed&quot; &amp;#x3C;&amp;#x3C; endl;
    }
};

// 中间类 C，虚继承 A
class C : virtual public A {
public:
    C(int value) : A(value) {
        cout &amp;#x3C;&amp;#x3C; &quot;C constructed&quot; &amp;#x3C;&amp;#x3C; endl;
    }
};

// 最远派生类 D，继承 B 和 C
class D : public B, public C {
public:
    // D 的构造函数需要显式初始化虚基类 A
    //最远派生类负责调用A的构造函数，保证只有一个A
    //先调用虚基类构造，再是直接基类，最后自身
    D(int value) : A(value), B(value), C(value) {
        cout &amp;#x3C;&amp;#x3C; &quot;D constructed&quot; &amp;#x3C;&amp;#x3C; endl;
    }
};

int main() {
    D d(10); // 创建 D 的对象
    cout &amp;#x3C;&amp;#x3C; &quot;Data in D: &quot; &amp;#x3C;&amp;#x3C; d.data &amp;#x3C;&amp;#x3C; endl; // 输出 10
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;第八章 多态&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;./6.png&quot; alt=&quot;6&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;编译时动态和运行时多态的本质就是静态类型检查和动态类型检查。&lt;/li&gt;
&lt;li&gt;**基态指针指向派生类对象是一种动态多态&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;基类指针可以指向派生类对象，但仍然调用的是基类成员函数&lt;/strong&gt;：但是由于编译时多态的原因，编译器不知道指针指向的对象已经重写了该成员函数&lt;/li&gt;
&lt;li&gt;可以使用**虚函数&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;运算符重载&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;运算符重载是对已有运算符赋予多重含义，并**没有新增运算符&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有部分运算符不能重载&lt;/strong&gt;，不能重载的运算符举例：`“.”、“.*”、“::”、“?:”、sizeof&lt;/li&gt;
&lt;li&gt;以下运算符**只允许用成员函数重载：&lt;code&gt;= ( ) [ ] new delete&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;重载之后运算符的优先级和结合性都不会改变&lt;/li&gt;
&lt;li&gt;两种重载方式：&lt;strong&gt;重载为类的非静态成员函数和重载为非成员函数。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;重载为非成员函数可以使用&lt;strong&gt;友元访问类的保护和私有成员，与成员函数的区别在于：成员函数有this指针，而友元函数没有this指针&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;一元运算符重载时：&lt;strong&gt;使用成员函数不必传入对象(有this指针)，而友元函数需要显示传入对象&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;二元运算符重载时：&lt;strong&gt;成员函数仅需要传入一个参数，而友元函数需要传入两个函数&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;重载的运算符函数 operator = &lt;strong&gt;不能被继承，且必须是成员函数&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;拷贝函数用于创建一个新对象&lt;/strong&gt;，赋值运算符是改变一个已存在的对象的值&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;虚函数&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;虚函数必须是非静态的成员函数,内联成员函数不能定义为虚函数&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;构造函数&lt;strong&gt;不能&lt;/strong&gt;是虚函数，析构函数&lt;strong&gt;可以是&lt;/strong&gt;虚函数&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;只有通过基类指针或引用，才能通过虚函数调用派生类对应函数&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;在派生类中可以对基类中的成员函数进行覆盖（重定义）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;每个多态类有一个虚表（virtual table),虚表中有当前类的各个虚函数的入口地址,每个对象有一个指向当前类的虚表的指针（虚指针vptr）&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;构造函数中为对象的虚指针赋值&lt;/strong&gt;,通过多态类型的指针或引用调用成员函数时，通过虚指针找到虚表，进而找到所调用的虚函数的入口地址,通过该入口地址调用虚函数&lt;/li&gt;
&lt;li&gt;派生类可以不显式地用virtual声明虚函数,编译器会根据函数名称、参数个数、对应参数类型、const限定符是否相同&lt;/li&gt;
&lt;li&gt;派生类中的虚函数还会隐藏基类中同名函数的所有其它重载形式。&lt;strong&gt;先查找派生类的作用域&lt;/strong&gt;，如果找到同名函数，就不再查找基类的作用域。适用于**成员函数、静态函数和虚函数。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;override&lt;/code&gt;关键字标记派生类中的虚函数，如果没有覆盖已经存在的虚函数，就会报错&lt;/li&gt;
&lt;li&gt;&lt;code&gt;final&lt;/code&gt;用于标记某个函数，则该函数不能再被它的派生类覆盖，否则编译器报错。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;纯虚函数和抽象类&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;纯虚函数&lt;code&gt;virtual 函数类型 函数名(参数表) = 0;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;具有纯虚函数的类成为抽象类，**只能作为基类使用，不能实例化对象&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;第九章 模板&lt;/h2&gt;
&lt;h3&gt;1. &lt;strong&gt;函数模板&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;函数模板允许定义一个通用的函数，可以用于多种数据类型。&lt;/p&gt;
&lt;h4&gt;语法&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;template &amp;#x3C;typename T&gt;
返回类型 函数名(参数列表) {
    // 函数体
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;template &amp;#x3C;typename T&gt;&lt;/code&gt;：声明一个模板，&lt;code&gt;T&lt;/code&gt; 是模板参数，表示任意类型。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;typename&lt;/code&gt; 可以用 &lt;code&gt;class&lt;/code&gt; 替换，两者在模板中含义相同。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;函数模板可以使用自动类型推导，优先调用普通函数，普通函数无法实现再调用函数模板&lt;/strong&gt;，函数模板也可以发生重载，但模板T不能有默认参数&lt;/p&gt;
&lt;h4&gt;示例：通用的 &lt;code&gt;max&lt;/code&gt; 函数&lt;/h4&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;#x3C;iostream&gt;
// 定义函数模板
template &amp;#x3C;typename T&gt;
T max(T a, T b) {
    return (a &gt; b) ? a : b;
}

int main() {
    std::cout &amp;#x3C;&amp;#x3C; max(3, 5) &amp;#x3C;&amp;#x3C; std::endl;         // 输出 5
    std::cout &amp;#x3C;&amp;#x3C; max(3.14, 2.71) &amp;#x3C;&amp;#x3C; std::endl;   // 输出 3.14
    std::cout &amp;#x3C;&amp;#x3C; max(&apos;a&apos;, &apos;z&apos;) &amp;#x3C;&amp;#x3C; std::endl;     // 输出 z
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;2. &lt;strong&gt;类模板&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;类模板允许定义一个通用的类，可以用于多种数据类型。&lt;/p&gt;
&lt;h4&gt;语法&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;template &amp;#x3C;typename T&gt;
class 类名 {
    // 类成员
};
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;template &amp;#x3C;typename T&gt;&lt;/code&gt;：声明一个模板，&lt;code&gt;T&lt;/code&gt; 是模板参数，表示任意类型。&lt;/li&gt;
&lt;li&gt;类模板的成员函数可以在类内定义（隐式内联），也可以在类外定义。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;类模板不能使用自动类型推导，必须显示指定类型&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;类模板中成员函数在调用时才创建&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;示例：通用的 &lt;code&gt;Box&lt;/code&gt; 类&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;#x3C;iostream&gt;
// 定义类模板
template &amp;#x3C;typename T&gt;
class Box {
private:
    T value;
public:
    Box(T v) : value(v) {}
    T getValue() const {
        return value;
    }
};

int main() {
    Box&amp;#x3C;int&gt; intBox(42);
    Box&amp;#x3C;std::string&gt; strBox(&quot;Hello, Templates!&quot;);

    std::cout &amp;#x3C;&amp;#x3C; intBox.getValue() &amp;#x3C;&amp;#x3C; std::endl;  // 输出 42
    std::cout &amp;#x3C;&amp;#x3C; strBox.getValue() &amp;#x3C;&amp;#x3C; std::endl;  // 输出 Hello, Templates!
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;3. &lt;strong&gt;模板参数&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;模板参数可以是类型参数（如 &lt;code&gt;typename T&lt;/code&gt;），也可以是非类型参数（如整数、指针等）。&lt;/p&gt;
&lt;h4&gt;示例：非类型模板参数&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;#x3C;iostream&gt;

// 非类型模板参数（整数）
template &amp;#x3C;typename T, int size&gt;
class Array {
private:
    T data[size];
public:
    void set(int index, T value) {
        data[index] = value;
    }
    T get(int index) const {
        return data[index];
    }
};

int main() {
    Array&amp;#x3C;int, 5&gt; intArray;
    intArray.set(0, 10);
    std::cout &amp;#x3C;&amp;#x3C; intArray.get(0) &amp;#x3C;&amp;#x3C; std::endl;  // 输出 10
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;4. &lt;strong&gt;模板特化&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;模板特化允许为特定类型提供特殊的实现。&lt;/p&gt;
&lt;h4&gt;示例：函数模板特化（重载）&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;#x3C;iostream&gt;
// 通用模板
template &amp;#x3C;typename T&gt;
void print(T value) {
    std::cout &amp;#x3C;&amp;#x3C; &quot;Generic: &quot; &amp;#x3C;&amp;#x3C; value &amp;#x3C;&amp;#x3C; std::endl;
}

// 特化版本（针对 const char*）
template &amp;#x3C;&gt;
void print&amp;#x3C;const char*&gt;(const char* value) {
    std::cout &amp;#x3C;&amp;#x3C; &quot;Specialized: &quot; &amp;#x3C;&amp;#x3C; value &amp;#x3C;&amp;#x3C; std::endl;
}

int main() {
    print(42);              // 输出 Generic: 42
    print(&quot;Hello, World!&quot;); // 输出 Specialized: Hello, World!
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;5. &lt;strong&gt;类模板的默认参数&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;可以为类模板参数指定默认值。&lt;/p&gt;
&lt;h4&gt;示例：类模板的默认参数&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;#x3C;iostream&gt;
template &amp;#x3C;typename T = int, int size = 10&gt;
class Array {
private:
    T data[size];
public:
    void set(int index, T value) {
        data[index] = value;
    }
    T get(int index) const {
        return data[index];
    }
};

int main() {
    Array&amp;#x3C;&gt; intArray; // 使用默认参数 T=int, size=10
    intArray.set(0, 42);
    std::cout &amp;#x3C;&amp;#x3C; intArray.get(0) &amp;#x3C;&amp;#x3C; std::endl;  // 输出 42
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;第十章 泛型程序设计与STL&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;STL基本组件：容器（container）、迭代器（iterator）、函数对象（function object）、算法（algorithms）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Iterators（迭代器）是算法和容器的桥梁。算法不直接操作容器中的数据，而是通过迭代器间接操作。&lt;/strong&gt; 将迭代器作为算法的参数、通过迭代器来访问容器而不是把容器直接作为算法的参数&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;函数对象（function object）：一个行为类似函数的对象，对它可以像调用函数一样调用&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;普通函数就是函数对象&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;重载了“()”运算符的类的实例是函数对象&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;numeric&gt;
using namespace std;

class MultClass { //定义MultClass类
public:
int operator() (int x, int y) const { return x * y; }
//重载操作符operator()
};

int main() {
int a[] = { 1, 2, 3, 4, 5 };
const int N = sizeof(a) / sizeof(int);
cout &amp;#x3C;&amp;#x3C; &quot;The result by multipling all elements in a is &quot;
&amp;#x3C;&amp;#x3C; accumulate(a, a + N, 1, MultClass()) &amp;#x3C;&amp;#x3C; endl;
//将类multclass传递给通用算法
return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./7.png&quot; alt=&quot;7&quot;&gt;&lt;/p&gt;
&lt;h2&gt;第十一章 流类库&lt;/h2&gt;
&lt;h3&gt;1. &lt;strong&gt;I/O流的概念及流类库结构&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;流的概念&lt;/strong&gt;：流是数据在生产者（如文件、键盘）和消费者（如程序、屏幕）之间流动的抽象。程序通过流对象与文件或设备进行交互。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;流类库结构&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;抽象流基类&lt;/strong&gt;：&lt;code&gt;ios&lt;/code&gt;是所有流类的基类。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;输入流类&lt;/strong&gt;：&lt;code&gt;istream&lt;/code&gt;（通用输入流）、&lt;code&gt;ifstream&lt;/code&gt;（文件输入流）、&lt;code&gt;istringstream&lt;/code&gt;（字符串输入流）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;输出流类&lt;/strong&gt;：&lt;code&gt;ostream&lt;/code&gt;（通用输出流）、&lt;code&gt;ofstream&lt;/code&gt;（文件输出流）、&lt;code&gt;ostringstream&lt;/code&gt;（字符串输出流）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;输入/输出流类&lt;/strong&gt;：&lt;code&gt;iostream&lt;/code&gt;（通用输入/输出流）、&lt;code&gt;fstream&lt;/code&gt;（文件输入/输出流）、&lt;code&gt;stringstream&lt;/code&gt;（字符串输入/输出流）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;流缓冲区类&lt;/strong&gt;：&lt;code&gt;streambuf&lt;/code&gt;（抽象流缓冲区基类）、&lt;code&gt;filebuf&lt;/code&gt;（磁盘文件流缓冲区）、&lt;code&gt;stringbuf&lt;/code&gt;（字符串流缓冲区）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;2. &lt;strong&gt;输出流&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;主要输出流对象&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cout&lt;/code&gt;：标准输出流，通常用于向屏幕输出。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cerr&lt;/code&gt;：标准错误输出流，无缓冲，立即输出。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;clog&lt;/code&gt;：标准错误输出流，有缓冲，缓冲区满时输出。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;文件输出流&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ofstream&lt;/code&gt;：用于向文件输出数据。可以通过构造函数或&lt;code&gt;open&lt;/code&gt;函数打开文件。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;示例：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;  ofstream myFile(&quot;filename&quot;);
  myFile &amp;#x3C;&amp;#x3C; &quot;Hello, World!&quot;;
  myFile.close();
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;输出格式控制&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;操纵符&lt;/strong&gt;：如&lt;code&gt;setw&lt;/code&gt;（设置输出宽度）、&lt;code&gt;setprecision&lt;/code&gt;（设置浮点数精度）、&lt;code&gt;setiosflags&lt;/code&gt;（设置对齐方式等）。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;cout &amp;#x3C;&amp;#x3C; setw(10) &amp;#x3C;&amp;#x3C; setiosflags(ios_base::left)&amp;#x3C;&amp;#x3C;setprecision(2) &amp;#x3C;&amp;#x3C; 3.14159; // 输出宽度为10，精度为2,左对齐，不指定fixed或scientific，精度值表示有效数字的位数,若指定了则是小数点后的位数
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;3. &lt;strong&gt;输入流&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;主要输入流对象&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;cin&lt;/code&gt;：标准输入流，通常用于从键盘输入。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ifstream&lt;/code&gt;：文件输入流，用于从文件读取数据。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;istringstream&lt;/code&gt;：字符串输入流，用于从字符串读取数据。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;提取运算符&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&gt;&gt;&lt;/code&gt;：用于从输入流中提取数据，默认以空白符为分隔符。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;输入流相关函数&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;get()&lt;/code&gt;：读取单个字符，包括空白符。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;getline()&lt;/code&gt;：读取一行数据，可以指定终止字符。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;read()&lt;/code&gt;：从文件中读取二进制数据到内存。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;istream&amp;#x26; read(char* buffer, streamsize size);&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;文件输入流&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ifstream&lt;/code&gt;：用于从文件读取数据。可以通过构造函数或&lt;code&gt;open&lt;/code&gt;函数打开文件。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ios_base::binary&lt;/code&gt;以二进制方式读取数据&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;示例：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;ifstream myFile(&quot;filename&quot;);
string line;
while (getline(myFile, line)) {
    cout &amp;#x3C;&amp;#x3C; line &amp;#x3C;&amp;#x3C; endl;
}
myFile.close();
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;4. &lt;strong&gt;输入/输出流&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;fstream&lt;/code&gt;&lt;/strong&gt;：同时支持文件的输入和输出操作。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;stringstream&lt;/code&gt;&lt;/strong&gt;：用于字符串的输入和输出，常用于数据类型转换。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;示例：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;stringstream ss;
ss &amp;#x3C;&amp;#x3C; 123;
int value;
ss &gt;&gt; value; // value = 123
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;第十二章 异常处理&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;./8.png&quot; alt=&quot;8&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;若有异常则通过throw操作创建一个异常对象并抛掷。&lt;/li&gt;
&lt;li&gt;**如果抛出异常后没有catch捕获，程序会调用 &lt;code&gt;std::terminate&lt;/code&gt;，默认行为是终止程序。&lt;/li&gt;
&lt;li&gt;将可能抛出异常的程序段嵌在try块之中。控制通过正常的顺序执行到达try语句，然后执行try块内的保护段。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;如果在保护段执行期间没有引起异常，那么跟在try块后的catch子句就不执行。程序从try块后跟随的最后一个catch子句后面的语句继续执行下去。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./9.png&quot; alt=&quot;9&quot;&gt;&lt;/p&gt;
&lt;h3&gt;异常处理中的构造与析构&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;初始化异常参数。&lt;/li&gt;
&lt;li&gt;将从对应的try块开始到异常被抛掷处之间构造（且尚未析构）的所有自动对象进行析构。&lt;/li&gt;
&lt;li&gt;从最后一个catch处理之后开始恢复执行。&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item></channel></rss>