strlen 函数的使用与模拟实现
目录
一、strlen 函数的基本特性
二、使用注意事项
补码是计算机表示有符号整数的标准方式,其核心规则是:(后面会讲解)
具体执行过程
为什么会出现这种情况
补码计算详细解释
三、strlen 的模拟实现
方法1:计数器方式
方法2:递归方式(不创建临时变量)
方法3:指针相减方式
四、实际应用建议
一、strlen 函数的基本特性
strlen
是 C 语言标准库中用于计算字符串长度的函数,其原型如下:
size_t strlen(const char *str);
该函数具有以下特点:
-
以 '\0' 为结束标志:字符串必须以空字符 '\0' 结尾,函数返回的是 '\0' 前面出现的字符个数(不包含 '\0' 本身)
-
返回值类型:返回值为
size_t
类型,这是一个无符号整型(unsigned integer),这在比较字符串长度时容易导致错误 -
头文件:使用前需要包含
<string.h>
头文件
二、使用注意事项
在这个例子中,关键问题在于 strlen()
返回的是 size_t
类型(无符号整数),而两个无符号整数相减的结果也是无符号整数,这会导致一些违反直觉的比较结果。
#include <stdio.h>
#include <string.h>int main()
{const char* str1 = "abcdef";const char* str2 = "bbb";if(strlen(str2) - strlen(str1) > 0){printf("str2 > str1\n"); // 这个分支会被执行}else{printf("str1 > str2\n");}return 0;
}
上述代码会输出 "str2 > str1",尽管直观上 "abcdef" 比 "bbb" 长。这是因为无符号数相减的结果也是无符号数,永远不会小于0。
补码是计算机表示有符号整数的标准方式,其核心规则是:(后面会讲解)
-
正数的补码 = 其二进制原码(和原码相同)
-
负数的补码 = 其绝对值的二进制表示取反(按位取反)后加 1
具体执行过程
-
strlen(str2)
返回 3(size_t
类型) -
strlen(str1)
返回 6(size_t
类型) -
计算
3 - 6
:-
对于无符号整数,这个操作不会产生负数
-
在32位系统中,
size_t
通常是32位无符号整数 -
3 - 6 的补码计算:
-
3 的二进制:
0000 0000 0000 0000 0000 0000 0000 0011
-
-6 的二进制(补码):
1111 1111 1111 1111 1111 1111 1111 1010
-
相加结果:
1111 1111 1111 1111 1111 1111 1111 1101
-
-
这个结果解释为无符号整数是一个很大的正数(4294967293)
-
-
比较
(4294967293 > 0)
结果为真
为什么会出现这种情况
-
无符号整数没有负数的概念,当减法结果为"负"时,会回绕到最大的无符号整数
-
这是C语言中常见的安全隐患,称为"无符号整数回绕"(unsigned integer wrap-around)
补码计算详细解释
在计算机中,负数用补码表示,减法实际上是加负数的补码:
-
计算 -6 的补码:
-
6 的二进制:
0000 0000 0000 0000 0000 0000 0000 0110
-
取反:
1111 1111 1111 1111 1111 1111 1111 1001
-
加1:
1111 1111 1111 1111 1111 1111 1111 1010
(这就是-6的补码)
-
-
计算 3 + (-6):
0000 0000 0000 0000 0000 0000 0000 0011 (3) + 1111 1111 1111 1111 1111 1111 1111 1010 (-6) -----------------------------------------1111 1111 1111 1111 1111 1111 1111 1101 (结果)
-
如果解释为有符号整数,这是-3
-
但作为无符号整数,这是4294967293
三、strlen 的模拟实现
方法1:计数器方式
#include <assert.h>
#include <stdio.h>int my_strlen(const char* str)
{int count = 0;assert(str != NULL); // 确保指针非空while (*str != '\0'){count++;str++;}return count;
}int main()
{const char* str1 = "abcdef";const char* str2 = "bbb";printf("%zu\n", my_strlen(str1));printf("%zu\n", my_strlen(str2));return 0;
}
方法2:递归方式(不创建临时变量)
#include <assert.h>
#include <stdio.h>int my_strlen(const char* str)
{assert(str != NULL);if (*str == '\0')return 0;elsereturn 1 + my_strlen(str + 1);
}int main()
{const char* str1 = "abcdef";const char* str2 = "bbb";printf("%zu\n", my_strlen(str1));printf("%zu\n", my_strlen(str2));return 0;
}
先拆分(递推),再回归(返回):(忘记了的话可以先回顾递归那一章节的知识点)
方法3:指针相减方式
#include <assert.h>
#include <stdio.h>int my_strlen(const char* str)
{assert(str != NULL);const char* p = str; // 使用const保持一致性while (*p != '\0')p++;return p - str; // 指针相减得到元素个数
}int main()
{const char* str1 = "abcdef";const char* str2 = "bbb";printf("%zu\n", my_strlen(str1));printf("%zu\n", my_strlen(str2));return 0;
}
四、实际应用建议
-
安全性:在实际项目中,应考虑添加参数检查(如使用assert)
-
const修饰:使用const修饰指针参数可以防止意外修改字符串内容
-
性能考虑:指针相减的方式通常效率最高,递归方式最不推荐在实际项目中使用
-
边界情况:处理空字符串("")时应返回0,处理NULL指针时应进行错误处理
这些实现方式展示了C语言中字符串操作的多种思路,理解它们有助于深入掌握指针和字符串处理的核心概念。