自己动手实现 strlen:从循环到递归的四种写法
🚀个人主页:BabyZZの秘密日记
📖收入专栏:C语言
🌍文章目入
- 1. 标准库版本:拿来主义
- 2. 计数器法:最直观的循环写法
- 3. 指针差值法:更“C语言”的写法
- 4. 递归法:一行代码的艺术
- 小结对比
在 C 语言里,strlen
几乎是每个初学者都会用到的函数:它用来计算一个以空字符 \0
结尾的字符串的长度。标准库里已经有现成的实现,但自己写一遍能帮助我们理解指针、循环、递归等核心概念。
今天,我们就从标准库版本出发,一步步写出四种不同的 my_strlen
,并比较它们的优缺点。
1. 标准库版本:拿来主义
#include <string.h>
int main()
{char str1[] = "abcdefg";size_t len = strlen(str1);printf("%zd\n", len); // 输出 7return 0;
}
这是最简单的调用方式,直接使用了 <string.h>
里的 strlen
实现。
2. 计数器法:最直观的循环写法
#include <string.h>
#include <assert.h>size_t my_strlen(const char* str1)
{assert(str1); // 防御空指针int count = 0; // 计数器while (*str1) // 遇到 '\0' 结束{count++;str1++;}return count;
}int main()
{char str1[] = "abcdefg";printf("%zd\n", my_strlen(str1)); // 输出 7return 0;
}
思路:
从字符串首地址开始,每读到一个非零字符,计数器加 1,直到遇到 \0
。
优点:
- 逻辑简单,容易理解。
- 时间复杂度 O(n),一次遍历即可。
缺点:
- 需要额外的计数器变量。
3. 指针差值法:更“C语言”的写法
#include <assert.h>size_t my_strlen(const char* str1)
{assert(str1);const char* end = str1;while (*end) // 找到 '\0'end++;return end - str1; // 指针 - 指针 = 元素个数
}int main()
{char str1[] = "abcdeqqqfg";printf("%zd\n", my_strlen(str1)); // 输出 10return 0;
}
思路:
让指针 end
一路走到字符串末尾,两个指针相减就是中间元素的个数。
优点:
- 不需要计数器,更贴近 C 指针运算的风格。
- 同样 O(n) 时间复杂度。
缺点:
- 可读性稍差,对初学者不够直观。
4. 递归法:一行代码的艺术
#include <assert.h>size_t my_strlen(const char* str1)
{assert(str1);if (*str1) // 非空字符return 1 + my_strlen(str1 + 1);return 0; // 遇到 '\0' 返回 0
}int main()
{char str1[] = "abcdeqqqfg";printf("%zd\n", my_strlen(str1)); // 输出 10return 0;
}
思路:
把问题拆成更小的子问题:
字符串长度 = 1 + 去掉首字符后的子串长度。
优点:
- 代码极简,一行解决。
- 逻辑优雅,适合面试炫技。
缺点:
- 递归深度等于字符串长度,可能栈溢出。
- 每次调用都有函数开销,性能不如循环。
小结对比
实现方式 | 时间复杂度 | 额外空间 | 栈深度 | 可读性 | 备注 |
---|---|---|---|---|---|
计数器 | O(n) | O(1) | 1 | ★★★★☆ | 最直观 |
指针差值 | O(n) | O(1) | 1 | ★★★☆☆ | 指针风格 |
递归 | O(n) | O(1) | O(n) | ★★★★☆ | 简洁但危险 |