一生一芯中有趣的C语言宏:LIST_FOREACH 链表遍历宏
<blockquote><p>记录了学习 “一生一芯” 时(更确切地说是学习 “Learn C The Hard Way” 时)遇到的 <strong><code>LIST_FOREACH</code> 链表遍历宏</strong>。该宏的精髓在于使用 <strong>V 和 _node 双指针机制</strong>,以确保即使在复杂场景下(如用户误改指针),<strong>循环的健壮性和遍历的正确性</strong>也不会被破坏。</p>
</blockquote>
<hr>
<h2 id="list_foreach的定义">LIST_FOREACH的定义</h2>
<pre><code class="language-C">#define LIST_FOREACH(L, S, M, V) ListNode *_node = NULL;\
ListNode *V = NULL;\
for(V = _node = L->S; _node != NULL; V = _node = _node->M)
</code></pre>
<p>这个定义出现在笨办法学C-中文版 练习32,用于双向链表的遍历。</p>
<h2 id="用途与宏展开">用途与宏展开</h2>
<h3 id="用途">用途</h3>
<p>这个宏定义实现了对双向链表的<strong>正向遍历</strong>和<strong>反向遍历</strong>,其中:</p>
<table>
<thead>
<tr>
<th style="text-align: center">参数</th>
<th style="text-align: center">含义</th>
<th style="text-align: center">示例</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center"><code>L</code></td>
<td style="text-align: center">链表结构体指针 (List)</td>
<td style="text-align: center"><code>list</code>(例如一个包含 <code>head</code> 和 <code>tail</code> 字段的结构体)</td>
</tr>
<tr>
<td style="text-align: center"><code>S</code></td>
<td style="text-align: center">起始结点名 (Start)</td>
<td style="text-align: center"><code>head</code> (如果从头开始遍历) 或 <code>tail</code> (如果从尾部开始遍历)</td>
</tr>
<tr>
<td style="text-align: center"><code>M</code></td>
<td style="text-align: center">移动到下一个节点的字段名 (Move)</td>
<td style="text-align: center"><code>next</code> (用于单向或向前遍历) 或 <code>prev</code> (用于向后遍历)</td>
</tr>
<tr>
<td style="text-align: center"><code>V</code></td>
<td style="text-align: center">当前节点变量名 (Variable)</td>
<td style="text-align: center"><code>cur</code> 或 <code>node</code> (用户在循环体中使用的迭代变量)</td>
</tr>
</tbody>
</table>
<h3 id="宏展开的逻辑分析">宏展开的逻辑分析</h3>
<ol>
<li>
<p><strong>局部变量声明</strong></p>
<pre><code class="language-C">ListNode *_node = NULL;
ListNode *V = NULL;
</code></pre>
<ul>
<li>
<p><strong><code>_node</code></strong>: 这是一个临时的内部工作变量,它总是指向<strong>下一个</strong>要检查的节点。在循环的初始化和步进部分,它用于<strong>保存</strong>和<strong>移动</strong>。</p>
</li>
<li>
<p><strong><code>V</code></strong>: 这是用户指定的<strong>当前节点</strong>变量。在每次循环迭代中,它指向用户当前处理的节点。</p>
</li>
</ul>
</li>
<li>
<p><strong><code>for</code>循环</strong></p>
<pre><code class="language-C">for(V = _node = L->S; _node != NULL; V = _node = _node->M)
</code></pre>
<p>很容易看懂。需要注意的是,运用了连等赋值的<strong>右结合性</strong>。例如,在步进部分 <code>V = _node = _node->M</code> 中:</p>
<ol>
<li>最右边先执行: <code>_node->M</code> 计算出<strong>下一个</strong>节点的地址。</li>
<li>向左赋值: 将该地址赋给 <code>_node</code>,完成 <strong><code>_node</code> 的移动</strong>。</li>
<li>再向左赋值: 将 <code>_node</code>(已更新)的值赋给 <code>V</code>,完成 <strong><code>V</code> 的同步</strong>。</li>
</ol>
</li>
</ol>
<h2 id="为什么同时存在v和_node">为什么同时存在<code>V</code>和<code>_node</code></h2>
<p>在这个宏中,<code>V</code>和<code>_node</code>两个宏变量是同步赋值的。那么,为什么需要同时设置这两个变量呢?</p>
<h3 id="宏变量v和_node的作用">宏变量<code>V</code>和<code>_node</code>的作用</h3>
<table>
<thead>
<tr>
<th>变量</th>
<th>完整名称/别名</th>
<th>作用/职责核心职能</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>V</code></td>
<td>用户变量/当前节点</td>
<td>供<strong>用户</strong>在循环体内部使用,代表<strong>当前正在处理的节点</strong>。<strong>接口</strong>:提供给用户操作。</td>
</tr>
<tr>
<td><code>_node</code></td>
<td>内部变量/工作指针</td>
<td>供<strong>宏内部</strong>使用,负责<strong>驱动</strong>循环的<strong>条件检查</strong>和<strong>步进计算</strong>。<strong>引擎</strong>:驱动循环逻辑。</td>
</tr>
</tbody>
</table>
<h3 id="健壮性考虑">健壮性考虑</h3>
<p>这样设计的原因可能是出于<strong>健壮性</strong>的考虑。如果只使用一个宏变量,<strong>例如只使用<code>V</code></strong>,当遍历过程中因为任何原因(例如调试、特殊逻辑、错误代码)<strong>修改了 <code>V</code> 的值</strong>(使其指向另一个节点,或者赋值为 <code>NULL</code>),那么步进表达式 <code>V = V->M</code> 将会<strong>彻底失败</strong>,导致:</p>
<ul>
<li><strong>遍历跳过</strong>节点。</li>
<li><strong>遍历提前终止</strong>。</li>
<li><strong>访问错误内存</strong>(如果 <code>V</code> 被置为非法指针)。</li>
</ul><br><br>
来源:https://www.cnblogs.com/Zeeh-Lin/p/19114180
頁:
[1]