C字符串长度 - strlen()
一、strlen() 函数概览
strlen() 是 C 标准库中用于计算字符串长度的函数,声明在 <string.h> 中。
c
size_t strlen(const char *str);
功能
计算字符串 str 的长度,直到遇到空结束字符 '\0' 为止,但不包括 '\0'。
参数
str:指向以'\0'结尾的字符串。
返回值
- 返回字符串中字符的个数,类型为
size_t。 size_t是无符号整数类型,因此不要用int接收后直接参与可能为负的比较。
示例:
c
#include <stdio.h>
#include <string.h>
int main(void)
{
const char *s = "hello";
printf("%zu\n", strlen(s)); // 输出 5
return 0;
}
二、strlen() 的模拟实现
理解 strlen() 的最好方式之一,就是自己模拟实现它。下面给出三种常见写法。
1. 计数器方式
c
#include <stddef.h>
size_t my_strlen(const char *str)
{
size_t count = 0;
while (*str != '\0') {
++count;
++str;
}
return count;
}
说明:
- 从字符串首地址开始逐个字符检查。
- 每遇到一个非
'\0'字符,计数器加一。 - 直到遇到
'\0'停止。 - 时间复杂度为 O(n)。
也可以写成:
c
while (*str) {
++count;
++str;
}
因为 '\0' 的 ASCII 值为 0,在条件判断中相当于假。
2. 递归方式
c
#include <stddef.h>
size_t my_strlen(const char *str)
{
if (*str == '\0')
return 0;
return 1 + my_strlen(str + 1);
}
说明:
- 如果当前字符是
'\0',说明到达字符串末尾,返回 0。 - 否则当前字符算作 1 个长度,再递归计算后续字符串长度。
- 代码简洁,但每次递归都会产生函数调用开销。
- 对于非常长的字符串,可能导致栈溢出,实际工程中不推荐。
3. 指针相减方式
c
#include <stddef.h>
size_t my_strlen(const char *s)
{
const char *p = s;
while (*p != '\0') {
++p;
}
return (size_t)(p - s);
}
说明:
- 用
p从字符串开头向后移动,直到遇到'\0'。 - 最终
p - s就是两个指针之间的元素个数,即字符串长度。 - 指针相减的结果类型是
ptrdiff_t,这里转换为size_t返回。 - 这种写法简洁高效,是常见的模拟实现方式。
三种模拟实现对比
| 实现方式 | 优点 | 缺点 |
|---|---|---|
| 计数器 | 直观易懂 | 需要额外计数变量 |
| 递归 | 代码简洁 | 栈开销大,长字符串可能溢出 |
| 指针相减 | 简洁高效 | 需要理解指针运算 |
三、sizeof 与 strlen 的核心区别
sizeof 和 strlen 经常被初学者混淆,但它们完全不同。
| 对比项 | sizeof | strlen |
|---|---|---|
| 本质 | 运算符 | 库函数 |
| 头文件 | 语言内置,无需头文件 | <string.h> |
| 计算内容 | 类型或对象占用的内存字节数 | 字符串中字符个数 |
是否包含 '\0' |
对字符串数组/字面量通常包含 | 不包含 |
| 适用类型 | 任意类型 | 只能用于以 '\0' 结尾的字符串 |
| 求值时机 | 通常编译时,VLA 除外 | 运行时 |
| 返回值类型 | size_t |
size_t |
| 是否受数组退化影响 | 数组名得到数组大小,指针得到指针大小 | 都按指向的字符串计算 |
四、典型示例
c
#include <stdio.h>
#include <string.h>
int main(void)
{
char s1[] = "hello";
char *s2 = "hello";
char s3[100] = "hello";
printf("sizeof(s1) = %zu\n", sizeof(s1)); // 6
printf("strlen(s1) = %zu\n", strlen(s1)); // 5
printf("sizeof(s2) = %zu\n", sizeof(s2)); // 4 或 8,取决于平台
printf("strlen(s2) = %zu\n", strlen(s2)); // 5
printf("sizeof(s3) = %zu\n", sizeof(s3)); // 100
printf("strlen(s3) = %zu\n", strlen(s3)); // 5
printf("sizeof(\"hello\") = %zu\n", sizeof("hello")); // 6
printf("strlen(\"hello\") = %zu\n", strlen("hello")); // 5
return 0;
}
关键点:
char s1[] = "hello";是字符数组,sizeof(s1)包含末尾的'\0',所以是 6。char *s2 = "hello";中s2是指针,sizeof(s2)得到的是指针大小,不是字符串长度。char s3[100] = "hello";中数组大小固定为 100,sizeof(s3)是 100,但strlen(s3)仍然是 5。
五、常见陷阱与注意事项
1. 字符串必须由 '\0' 结尾
c
char buf[5] = {'h', 'e', 'l', 'l', 'o'};
printf("%zu\n", strlen(buf)); // 错误:buf 没有 '\0',越界访问,行为未定义
strlen() 会一直向后寻找 '\0',如果字符串没有正确终止,就会越界读取,导致未定义行为。
2. 返回类型是 size_t,不是 int
c
if (strlen(s) - 1 >= 0) {
// 可能永远为真
}
因为 strlen() 返回 size_t,strlen(s) - 1 仍然是无符号数。如果 strlen(s) 为 0,结果会变成一个很大的正数。
建议显式比较:
c
if (strlen(s) > 0) {
// ...
}
或先转换为有符号类型,但要确保长度不会溢出。
3. 函数参数中的数组会退化为指针
c
void f(char arr[])
{
printf("%zu\n", sizeof(arr)); // 输出指针大小,不是数组大小
}
在函数参数中,char arr[] 等价于 char *arr,所以 sizeof 得到的是指针大小。
4. strlen 不适用于二进制数据
如果数据中间可能包含 '\0',例如二进制文件内容、网络协议包等,不能使用 strlen() 计算长度。应使用 memchr() 或额外保存长度。
5. 标准库实现可能经过高度优化
标准库中的 strlen() 通常不是简单的逐字节循环,可能按机器字长读取并检测零字节,以提高性能。但语义始终不变:返回 '\0' 之前的字符数。
六、总结
strlen()用于计算以'\0'结尾的字符串长度,不包含'\0'。- 可以通过计数器、递归、指针相减等方式模拟实现。
- 工程中推荐使用标准库
strlen(),并注意其返回类型是size_t。 sizeof是运算符,计算对象或类型占用的字节数;strlen是函数,计算字符串字符个数。- 使用
strlen()前必须确保字符串已经以'\0'正确结尾,否则会越界访问,导致未定义行为。
一句话记忆:
sizeof看内存大小,strlen数字符个数;sizeof常含'\0',strlen不含'\0'。