检查两字符串是否为anagram的精简C代码逻辑及特殊for循环原理解析
精简C语言Anagram判定代码逐段解析
前置已知逻辑对齐
你之前梳理的全局数组频次统计逻辑是对的:代码利用全局变量默认初始化值为0的特性,用字符的ASCII值作为t[256]的索引,第一个输入串的字符频次累加正值,第二个输入串的字符频次累加负值,最终所有位置频次为0,就代表两个字符串的字符组成完全一致。
这里补全你没拆解到的隐式设计:
- main函数的形参
c本质是程序启动时的argc(命令行参数计数),程序正常运行时初始值为1,直接被拿来当频次统计的权重值,省了单独开状态标记变量的开销。 - 第一段输入循环的终止逻辑:循环判定条件是
c+3,也就是当c的值让c+3为0时就停止输入读取。整个执行流程是:- 初始c=1,读到非换行(ASCII>10)的字符就给对应
t[i] +=1,统计第一个字符串的所有字符; - 读到第一个换行符(ASCII=10,不满足
>10的条件),执行c -=2,c从1变成-1,这时候c+3=2,循环继续; - 之后读到的非换行字符,给对应
t[i] +=-1,也就是统计第二个字符串的字符频次做减法; - 读到第二个换行符,再执行一次
c -=2,c从-1变成-3,这时候c+3=0,循环直接终止,刚好完成两个字符串的输入读取,不会多跑一次。
- 初始c=1,读到非换行(ASCII>10)的字符就给对应
核心疑问:结果判定循环for(i=257;--i&&!t[i-1];);的完整执行逻辑
这段代码把所有逻辑都压进了for循环的判定条件里,循环体是空的(末尾的分号就是空体),拆解后每一步的执行顺序、变量变化、判定逻辑如下:
1. 循环初始化
直接给i赋值257,没有其他操作。选这个初始值,就是为了配合前置自减的执行逻辑,刚好能完整扫过t[0]到t[255]的全部256个数组元素,同时避开数组越界问题。
2. 循环判定的执行规则
for循环每次执行前都会先计算判定条件,条件为真就跑空循环体、回到判定步骤;条件为假就直接跳出循环。这个判定条件是两个表达式用短路与&&连接——也就是左边的表达式为真时,才会计算右边的表达式:
- 第一步永远先算左边的
--i:每一次判定开始,先把i做一次前置自减,用自减后的新值作为表达式的结果。 - 第二步当左边的
--i结果为非0时,才计算右边的!t[i-1]:用自减后的i值,取i-1作为索引,检查对应的t数组位置的频次值——如果这个位置的值是0,取反后就是真,代表当前检查的字符频次是正负抵消的;如果值非0,取反后就是假,代表有字符的两个字符串频次对不上。
3. 变量i的完整取值流转
把从初始值到循环跳出的全流程列出来:
- 初始i=257,第一次判定:
- 执行
--i:i变成256,表达式值256(非0,真); - 计算右边:检查
t[256-1=255]的频次,如果是0,整个条件为真,继续循环。
- 执行
- 第二次判定:
- 执行
--i:i变成255,表达式值255(真); - 计算右边:检查
t[255-1=254]的频次,为0就继续循环。
- 执行
- 按照上述规则持续循环:每次i减1,依次检查
t[253]、t[252]……一直到索引0的位置。 - 两种循环跳出的场景:
- 场景1:所有位置的t值都是0。当检查完
t[0]之后,下一次判定会执行--i,把i从1减到0,这时候左边的表达式结果是0(假),因为短路与的特性,根本不会执行右边的索引计算(也就是不会访问越界的t[-1]),直接跳出循环。这时候i的值是0,后面的puts(i?"false":"true")看到i为0,就输出true,代表两个字符串是anagram。 - 场景2:中途发现某个t位置的值非0。比如检查到
t[65](对应大写字母A的ASCII值)的值不为0,这时候自减后的i是66,右边的!t[65]结果为假,循环直接跳出。这时候i的值是66(非0),后面的puts看到i非0,就输出false,代表两个字符串不是anagram。
- 场景1:所有位置的t值都是0。当检查完
关键安全设计
很多人好奇这段代码为什么不会越界访问数组:正是因为用了短路与的执行特性,当--i把i减到0的时候,右边的索引计算根本不会执行,完全避开了t[-1]的非法访问。
内容的提问来源于stack exchange,提问作者Neon
相关产品推荐
相关产品推荐

