【C语言】字符函数和字符串函数
本文系统介绍了C语言中常用的字符串处理函数,包括字符分类、转换函数以及各类字符串操作函数。重点分析了strlen、strcpy、strcat、strcmp等核心函数的原型特点、使用示例、注意事项和模拟实现方法,并详细讲解了strncpy、strncat、strncmp等安全版本函数。此外还介绍了字符串查找(strstr)、分割(strtok)和错误处理(strerror)等实用函数。文章通过大量代
目录
十、strstr的使用和模拟实现(重要,由于可以查找字符串,所以可以用于日志)
十一、strtok函数的使用(重要,由于可以分割字符串,所以可用于解析命令行)
一、字符分类函数
| 函数名 | 功能描述 | 示例字符 |
|---|---|---|
| iscntrl | 检查字符是否为控制字符(非打印字符) | \n, \t, \0 |
| isspace | 检查字符是否为空白字符 | 空格' ', \t, \n |
| isdigit | 检查字符是否为十进制数字(0-9) | '0', '5', '9' |
| isxdigit | 检查字符是否为十六进制数字(0-9, a-f, A-F) | 'A', 'f', '3' |
| islower | 检查字符是否为小写字母(a-z) | 'a', 'z', 'm' |
| isupper | 检查字符是否为大写字母(A-Z) | 'A', 'Z', 'M' |
| isalpha | 检查字符是否为字母(a-z或A-Z) | 'a', 'Z', 'G' |
| isalnum | 检查字符是否为字母或数字 | 'a', '5', 'Z' |
| ispunct | 检查字符是否为标点符号(除空格、数字、字母外的可打印字符) | '.', '!', '@' |
| isgraph | 检查字符是否为图形字符(除空格外的可打印字符) | 'a', '!', '5' |
| isprint | 检查字符是否为可打印字符(包括空格) | 'a', ' ', '?' |
-
这些函数都定义在
<ctype.h>头文件中 -
函数参数为
int类型,但实际上是字符的ASCII值 -
返回值:非零值表示真,0表示假
-
注意:
isgraph不包括空格,isprint包括空格 -
所有函数对EOF(通常为-1)都返回0
二、字符转换函数
int tolower ( int c ); //将参数传进去的大写字母转小写
int toupper ( int c ); //将参数传进去的小写字母转大写
三、strlen的使用和模拟实现(重要)
1.函数原型与特点
原型:
函数原型 size_t strlen(const char *str);
参数:
str- 要计算长度的字符串(必须以空字符\0结尾,如果不包含\0会导致越界访问)返回值: 字符串的长度(不统计结尾的空字符
\0)返回类型:
size_t- 无符号整数类型(这意味着不能使用两个函数做减法)特点:
- 不统计\0
- 被统计字符串需要以\0结尾
- 返回值类型是size_t
2.使用示例
#include <stdio.h>
#include <string.h>
int main() {
char str1[] = "Hello";
char *str2 = "World";
printf("Length of \"%s\": %zu\n", str1, strlen(str1)); // 输出: 5
printf("Length of \"%s\": %zu\n", str2, strlen(str2)); // 输出: 5
printf("Length of empty string: %zu\n", strlen("")); // 输出: 0
return 0;
}
3.注意事项与常见错误
/****************************** 1.不可使用未初始化的指针 ******************************/
char *str; // 未初始化
printf("%zu\n", strlen(str)); // 未定义行为!
/****************************** 2.不可数组越界访问 ******************************/
char str[10];
scanf("%s", str); // 如果输入超过9个字符,\0会被写在数组外
printf("%zu\n", strlen(str)); // 可能出错
/****************************** 3.不可用于统计非字符串 ******************************/
int arr[] = {1, 2, 3, 4, 5};
printf("%zu\n", strlen((char*)arr)); // 错误!这不是字符串
4.模拟实现
/***************************** 方法一:计数器 *****************************/
size_t my_strlen_counter(const char *str) {
size_t count = 0;
// 当遇到\0时停止计数
while (*str != '\0') {
count++;
str++;
}
return count;
}
/***************************** 方法二:指针相减 *****************************/
size_t my_strlen_pointer(const char *str) {
const char *start = str; // 记录起始位置
// 移动指针直到遇到\0
while (*str != '\0') {
str++;
}
// 指针相减得到长度
return (size_t)(str - start);
}
/***************************** 方法三:递归(不推荐) *****************************/
size_t my_strlen_recursive(const char *str) {
if (*str == '\0') {
return 0;
}
return 1 + my_strlen_recursive(str + 1);
}
5.与sizeof的区别
| 特性 | strlen | sizeof |
|---|---|---|
| 用途 | 计算字符串长度 | 计算数据类型/对象占用的字节数 |
| 返回值 | 运行时计算 | 编译时确定 |
| 统计时是否包含\0 | 不包含 | 包含(对于字符数组) |
| 参数 | 必须是字符串 | 可以是类型或变量 |
| 示例 | strlen("abc")返回3 |
sizeof("abc")返回4 |
四、strcpy的使用和模拟实现(重要)
1.函数原型与特点
原型:
char *strcpy(char *dest, const char *src);
参数:
dest- 目标字符串的地址,用于存储复制的内容。
src- 源字符串的地址,从中复制内容。返回值: 返回目标字符串的起始地址(即
dest的地址)。功能: 将源字符串(包括结尾的空字符
\0)复制到目标字符串中。特点:
- 复制包括\0
- 源字符串必须以\0结尾
- 目标数组必须足够大以防止溢出
- 目标数组必须可被修改,即不能是常量字符串
- 源和目标不能有重叠的地址
char str[] = "hello"; strcpy(str, str+1); // 未定义行为,因为源和目标重叠
2.使用示例
#include <stdio.h>
#include <string.h>
int main() {
char src[] = "Hello, World!";
char dest[20]; // 确保目标数组足够大
strcpy(dest, src);
printf("Source: %s\n", src); // 输出: Hello, World!
printf("Destination: %s\n", dest); // 输出: Hello, World!
// 支持链式调用
char dest2[20];
printf("Chained: %s\n", strcpy(dest2, src));
return 0;
}
3.注意事项与常见错误
/****************************** 1.目标缓冲区不能太小 ******************************/
char src[50] = "This is a very long string that exceeds the destination buffer size";
char dest[10];
strcpy(dest, src); // 缓冲区溢出,可能导致程序崩溃或安全漏洞
/****************************** 2.源字符串不能没有\0 ******************************/
char src[5] = {'H','e','l','l','o'};
char dest[10];
strcpy(dest, src); // 会一直复制直到在内存中遇到\0,可能复制大量数据
/****************************** 3.源和目标不能有重叠的地址 ******************************/
char str[20] = "Hello, World!";
strcpy(str + 7, str); // 未定义行为
/****************************** 4.目标地址不能未分配内存 ******************************/
char *dest; // 未分配内存
strcpy(dest, "Hello"); // 未定义行为,dest是野指针
4.模拟实现
/***************************** 方法一:挨个复制 *****************************/
char *my_strcpy1(char *dest, const char *src) {
// 保存目标字符串的起始地址
char *start = dest;
// 逐个复制字符,包括\0
while (*src != '\0') {
*dest = *src;
dest++;
src++;
}
*dest = '\0'; // 复制结束符
return start;
}
/***************************** 方法二:使用指针进行复制 *****************************/
char *my_strcpy2(char *dest, const char *src) {
char *start = dest;
while ((*dest++ = *src++) != '\0');
return start;
}
/***************************** 方法三:使用数组进行复制 *****************************/
char *my_strcpy3(char *dest, const char *src) {
int i = 0;
while (src[i] != '\0') {
dest[i] = src[i];
i++;
}
dest[i] = '\0';
return dest;
}
五、strcat的使用和模拟实现(重要)
1.函数原型与特点
原型:
char *strcat(char *dest, const char *src);
参数:
dest- 目标字符串,必须足够大以容纳连接后的结果,且必须以空字符结尾
src- 源字符串,必须以空字符结尾返回值: 返回目标字符串的起始地址(即
dest的地址)功能: 将源字符串
src连接到目标字符串dest的末尾(从dest的\0位置开始复制)特点:
- 源字符串必须以空字符\0结尾
- 目标字符串必须足够大
- 目标字符串必须以空字符结尾
- 源地址和目标地址可以重叠(但目标地址必须在源地址之前)(对于前后还不太理解,先记不允许重叠)
// 正确示例:src在dest后面,不重叠 char str[20] = "Hello"; strcat(str, " World"); // 正常 // 错误示例:源和目标重叠,且源在目标前面 char str[20] = "Hello"; strcat(str, str+2); // 未定义行为
2.使用示例
#include <stdio.h>
#include <string.h>
int main() {
char dest[20] = "Hello"; // 注意:dest必须足够大
char src[] = " World!";
strcat(dest, src);
printf("Result: %s\n", dest); // 输出: Hello World!
// 链式调用
char dest2[30] = "Hello";
printf("Chained: %s\n", strcat(dest2, " World!"));
// 多次连接
strcat(dest, " How are you?");
printf("Multiple concatenation: %s\n", dest);
return 0;
}
3.注意事项与常见错误
/****************************** 1.目标缓冲区不能太小 ******************************/
char dest[10] = "Hello";
char src[50] = " World! This is a very long string.";
strcat(dest, src); // 缓冲区溢出
/****************************** 2.源字符串不能没有\0 ******************************/
char dest[20] = "Hello";
char src[5] = {' ','W','o','r','l'}; // 没有\0
strcat(dest, src); // 未定义行为,复制不会停止
/****************************** 3.目标字符串不能没有\0 ******************************/
char dest[10] = {'H','e','l','l','o'}; // 没有\0
char src[] = " World";
strcat(dest, src); // 未定义行为,因为dest没有\0结尾,找不到连接点
/****************************** 4.源地址和目标地址不能重叠 ******************************/
char str[20] = "Hello";
strcat(str, str+2); // 未定义行为
/****************************** 5.目标字符串不能为常量 ******************************/
char *dest = "Hello"; // 字符串字面量,只读
strcat(dest, " World"); // 运行时错误,试图修改只读内存
/****************************** 6.目标地址不能未分配内存 ******************************/
char *dest; // 未初始化
strcat(dest, "Hello"); // 未定义行为,dest是野指针
4.模拟实现
/***************************** 方法一:挨个遍历 *****************************/
char *my_strcat1(char *dest, const char *src) {
// 保存目标字符串的起始地址
char *start = dest;
// 1. 找到dest的结尾(即\0的位置)
while (*dest != '\0') {
dest++;
}
// 2. 从dest的结尾开始复制src,包括\0
while (*src != '\0') {
*dest = *src;
dest++;
src++;
}
*dest = '\0'; // 复制结束符
return start;
}
/***************************** 方法二:使用指针 *****************************/
char *my_strcat2(char *dest, const char *src) {
char *start = dest;
// 移动到dest的末尾
while (*dest) {
dest++;
}
// 复制src到dest末尾
while ((*dest++ = *src++) != '\0');
return start;
}
/***************************** 方法三:使用数组 *****************************/
char *my_strcat3(char *dest, const char *src) {
int i = 0;
int j = 0;
// 找到dest的结尾
while (dest[i] != '\0') {
i++;
}
// 从dest的结尾开始复制src
while (src[j] != '\0') {
dest[i] = src[j];
i++;
j++;
}
dest[i] = '\0';
return dest;
}
六、strcmp的使用和模拟实现(重要)
1.函数原型与特点
原型:
int strcmp(const char *str1, const char *str2);
参数:
str1- 要比较的第一个字符串
str2- 要比较的第二个字符串返回值:
若
str1等于str2,则返回0若
str1小于str2,则返回负数若
str1大于str2,则返回正数功能: 按字符的ASCII值比较两个字符串
特点:
- 比较基于字符的ASCII值
- 返回值不一定是-1、0、1
2.使用示例
#include <stdio.h>
#include <string.h>
int main() {
char str1[] = "apple";
char str2[] = "banana";
char str3[] = "apple";
int result1 = strcmp(str1, str2); // 负数 ("apple" < "banana")
int result2 = strcmp(str2, str1); // 正数 ("banana" > "apple")
int result3 = strcmp(str1, str3); // 0 ("apple" == "apple")
printf("strcmp(\"%s\", \"%s\") = %d\n", str1, str2, result1);
printf("strcmp(\"%s\", \"%s\") = %d\n", str2, str1, result2);
printf("strcmp(\"%s\", \"%s\") = %d\n", str1, str3, result3);
// 在条件判断中的典型用法
if (strcmp(str1, str2) < 0) {
printf("\"%s\" comes before \"%s\"\n", str1, str2);
}
return 0;
}
3.注意事项与常见错误
/****************************** 1.误以为返回值是-1、0、1 ******************************/
// 错误:检查具体的返回值
if (strcmp(str1, str2) == 1) { // 错误!不一定返回1
printf("str1 > str2\n");
}
// 错误:用布尔值判断
if (strcmp(str1, str2)) { // 错误!0表示相等,非0表示不相等
printf("Strings are different\n");
}
// 正确:检查符号
if (strcmp(str1, str2) < 0) { // 正确
printf("str1 < str2\n");
} else if (strcmp(str1, str2) > 0) { // 正确
printf("str1 > str2\n");
} else { // 正确
printf("str1 == str2\n");
}
/****************************** 2.不能比较未初始化的字符串且字符串必须以\0结尾 ******************************/
// 错误:未初始化的指针
char *str1; // 未初始化
char *str2 = "hello";
int result = strcmp(str1, str2); // 未定义行为
// 错误:数组未以\0结尾
char arr1[3] = {'a', 'b', 'c'}; // 没有\0
char arr2[3] = {'a', 'b', 'c'};
int result = strcmp(arr1, arr2); // 未定义行为,可能越界访问
/****************************** 3.大小写敏感 ******************************/
// strcmp是大小写敏感的
strcmp("Hello", "hello"); // 返回非0值('H' != 'h')
// 如果需要不区分大小写,使用stricmp或strcasecmp(非标准)
// 或者自定义函数
4.模拟实现
/***************************** 方法一:逐个字符比较 *****************************/
int my_strcmp1(const char *str1, const char *str2) {
// 逐字符比较,直到遇到不同的字符或字符串结束
while (*str1 != '\0' && *str2 != '\0' && *str1 == *str2) {
str1++;
str2++;
}
// 返回字符的ASCII差值
return *str1 - *str2;
}
/***************************** 方法二:使用指针进行比较 *****************************/
int my_strcmp2(const char *str1, const char *str2) {
while (*str1 && *str1 == *str2) {
str1++;
str2++;
}
return *(unsigned char *)str1 - *(unsigned char *)str2;
// 注意:转换为unsigned char*是为了正确处理大于127的字符
}
/***************************** 方法三:使用数组索引进行比较 *****************************/
int my_strcmp3(const char *str1, const char *str2) {
int i = 0;
// 当两个字符相同且都不为\0时继续比较
while (str1[i] != '\0' && str2[i] != '\0' && str1[i] == str2[i]) {
i++;
}
return (unsigned char)str1[i] - (unsigned char)str2[i];
}
七、strncpy函数的使用(指定字符个数拷贝)
1.函数原型与特点
原型:
char *strncpy(char *dest, const char *src, size_t n);
参数:
dest- 目标字符串的地址
src- 源字符串的地址
n- 最多复制的字符数返回值: 返回目标字符串的起始地址(即
dest的地址)功能: 从源字符串复制最多
n个字符到目标字符串特点:
- 源字符串必须以\0结尾
- 当n<源字符串长度时,按照要求将n个字符拷贝到目标字符串,不会填充\0
- 当n≥源字符串长度时,多出的部分用\0填充
2.使用示例
#include <stdio.h>
#include <string.h>
int main() {
char src[] = "Hello, World!";
char dest[20];
// 复制最多10个字符
strncpy(dest, src, 10);
printf("Result: %s\n", dest); // 输出: Hello, Wo (没有\0结尾)
// 安全用法:手动添加\0
dest[10] = '\0';
printf("Safe result: %s\n", dest); // 输出: Hello, Wo
// 复制整个字符串(包括\0)
strncpy(dest, src, strlen(src) + 1); // +1 复制\0
printf("Full copy: %s\n", dest);
return 0;
}
3.注意事项与常见错误
/****************************** 1.性能问题 ******************************/
// 如果src很短而n很大,会有很多不必要的\0填充
char dest[10000];
strncpy(dest, "Hi", 10000); // 填充9998个\0,性能差
/****************************** 2.目标缓冲区不能太小 ******************************/
// 错误:目标缓冲区太小,strncpy不检查目标缓冲区大小
char src[100] = "A very long string...";
char dest[10];
strncpy(dest, src, sizeof(dest)); // 复制10个字符,没有\0
// 第10个字符不是\0,dest不是有效字符串
// 正确:预留一个位置给\0
char dest2[11];
strncpy(dest2, src, sizeof(dest2) - 1);
dest2[sizeof(dest2) - 1] = '\0';
/****************************** 3.strncpy不一定有\0 ******************************/
// 错误:如果src长度 >= n,dest没有\0结尾
char src[] = "Hello World";
char dest[5];
strncpy(dest, src, 5); // dest现在没有\0
printf("%s\n", dest); // 未定义行为!可能打印乱码或导致程序崩溃
// 正确:手动添加\0
char dest_safe[6];
strncpy(dest_safe, src, 5);
dest_safe[5] = '\0'; // 手动确保以\0结尾
printf("%s\n", dest_safe); // 正确:Hello
4.与strcpy、memcpy的区别
| 特性 | strcpy | strncpy | memcpy |
|---|---|---|---|
| 安全性 | 不安全,容易缓冲区溢出 | 相对安全,可指定最大长度 | 安全,需要指定长度 |
| 自动添加\0 | 总是添加 | 只有源长度小于n时添加 | 从不添加 |
| 填充行为 | 无 | 用\0填充剩余部分 | 无填充 |
| 停止条件 | 遇到\0停止 | 复制n个字符或遇到\0 | 复制指定字节数 |
| 用途 | 字符串复制 | 安全字符串复制 | 内存块复制 |
八、strncat函数的使用(指定字符个数连接)
1.函数原型与特点
原型:
char *strncat(char *dest, const char *src, size_t n);
参数:
dest- 目标字符串,必须足够大以容纳连接后的结果
src- 源字符串,要追加到目标字符串的内容
n- 最多从src追加的字符数返回值: 返回目标字符串的起始地址(即
dest的地址)功能: 将源字符串
src的前n个字符追加到目标字符串dest的末尾,并在新字符串的末尾添加一个\0特点:
- 总是以空字符\0结尾
- 当n<源字符串长度时,按照要求将n个字符连接到目标字符串后面,然后填充\0
- 当n≥源字符串长度时,在末尾填充完成\0后多出的部分继续用\0填充
2.使用示例
#include <stdio.h>
#include <string.h>
int main() {
char dest[20] = "Hello";
char src[] = " World! This is a long string.";
// 安全地追加,最多追加7个字符
strncat(dest, src, 7);
printf("Result: %s\n", dest); // 输出: Hello World!
// 计算剩余空间的安全追加方式
char dest2[15] = "Hello";
size_t dest_size = sizeof(dest2);
size_t dest_len = strlen(dest2);
size_t max_append = dest_size - dest_len - 1; // -1为\0预留
strncat(dest2, " World!", max_append);
printf("Safe append: %s\n", dest2);
// 链式调用
char dest3[30] = "Hello";
printf("Chained: %s\n", strncat(dest3, " World!", 10));
return 0;
}
3.注意事项与常见错误
/****************************** 1.目标缓冲区不能太小 ******************************/
// 错误:没有考虑dest的剩余空间
char dest[10] = "Hello";
strncat(dest, " World!", 7); // 缓冲区溢出!需要14个字符空间
// 正确:计算剩余空间
char dest_safe[10] = "Hello";
size_t remaining = sizeof(dest_safe) - strlen(dest_safe) - 1; // -1为\0预留
strncat(dest_safe, " World!", remaining); // 安全
/****************************** 2.源字符串中不能没有\0 ******************************/
char dest[20] = "Hello";
char src[5] = {'W', 'o', 'r', 'l', 'd'}; // 没有\0
strncat(dest, src, 5); // 未定义行为,可能复制超过5个字符
/****************************** 3.目标字符串种不能没有\0 ******************************/
char dest[10] = {'H', 'e', 'l', 'l', 'o'}; // 没有\0
strncat(dest, " World", 6); // 未定义行为,找不到dest的结尾
4.与strcat、snprintf的区别
| 特性 | strcat | strncat | snprintf |
|---|---|---|---|
| 安全性 | 不安全,无长度限制 | 相对安全,可指定最大追加字符数 | 安全,可指定整个缓冲区大小 |
| 自动处理\0 | 总是添加 | 总是添加 | 总是添加 |
| 性能 | 较快 | 中等 | 较慢(需要解析格式) |
| 灵活性 | 低 | 中等 | 高(支持格式化) |
| 缓冲区检查 | 无 | 有限(只检查追加长度) | 完整检查 |
九、strncmp函数的使用(指定字符个数比较)
1.函数原型与特点
原型:
int strncmp(const char *str1, const char *str2, size_t n);
参数:
str1- 要比较的第一个字符串
str2- 要比较的第二个字符串
n- 最多比较的字符数返回值:
若前n个字符相等,则返回0
若发现不同字符,返回字符的ASCII差值(str1 - str2)
功能: 比较两个字符串的前n个字符或直到遇到空字符为止
特点:
- 比较基于字符的ASCII值
- 返回值不一定是-1、0、1
2.使用示例
#include <stdio.h>
#include <string.h>
int main() {
char str1[] = "Hello, World!";
char str2[] = "Hello, C!";
// 比较前7个字符
int result1 = strncmp(str1, str2, 7);
printf("strncmp(\"%s\", \"%s\", 7) = %d\n", str1, str2, result1); // 输出: 0
// 比较前8个字符
int result2 = strncmp(str1, str2, 8);
printf("strncmp(\"%s\", \"%s\", 8) = %d\n", str1, str2, result2); // 输出: 正数
// 比较整个字符串
int result3 = strncmp(str1, str2, strlen(str1));
printf("strncmp(\"%s\", \"%s\", %zu) = %d\n", str1, str2, strlen(str1), result3);
// 部分匹配
char str3[] = "Hello";
int result4 = strncmp(str1, str3, 5);
printf("strncmp(\"%s\", \"%s\", 5) = %d\n", str1, str3, result4); // 输出: 0
return 0;
}
3.注意事项与常见错误
/****************************** 1.误以为返回值是-1、0、1 ******************************/
// 错误:检查具体的返回值
if (strncmp(str1, str2, n) == 1) { // 错误!可能不返回1
// ...
}
// 正确:检查符号
if (strncmp(str1, str2, n) < 0) { // str1 < str2
// ...
} else if (strncmp(str1, str2, n) > 0) { // str1 > str2
// ...
} else { // str1 == str2
// ...
}
/****************************** 2.不能比较未初始化的字符串且字符串必须以\0结尾 ******************************/
// 错误:未初始化的指针
char *str1; // 未初始化
char *str2 = "hello";
int result = strncmp(str1, str2); // 未定义行为
// 错误:数组未以\0结尾
char arr1[3] = {'a', 'b', 'c'}; // 没有\0
char arr2[3] = {'a', 'b', 'c'};
int result = strncmp(arr1, arr2); // 未定义行为,可能越界访问
/****************************** 3.大小写敏感 ******************************/
// strncmp是大小写敏感的
strncmp("Hello", "hello", 5); // 返回非0值
// 如果需要不区分大小写,使用strncasecmp(非标准)
// 或者自定义函数
4.与strcmp、memcmp的区别
| 特性 | strcmp | strncmp | memcmp |
|---|---|---|---|
| 比较范围 | 整个字符串(直到\0) | 前n个字符或直到\0 | 前n个字节 |
| 停止条件 | 遇到\0或字符不同 | 比较n个字符或遇到\0 | 比较n个字节或字符不同 |
| \0的处理 | 作为字符串结束符 | 作为字符串结束符 | 作为普通字节 |
| 安全性 | 需要字符串以\0结尾 | 可以处理没有\0的数据 | 完全控制比较长度 |
| 性能 | 通常较快 | 中等 | 可能更快 |
十、strstr的使用和模拟实现(重要,由于可以查找字符串,所以可以用于日志)
1.函数原型与特点
原型:
char *strstr(const char *haystack, const char *needle);
参数:
haystack- 被查找的主字符串(干草堆)
needle- 要查找的子字符串(针)返回值:
如果在
haystack中找到needle,返回指向第一次出现位置的指针如果未找到,返回
NULL如果
needle是空字符串,返回haystack功能: 在主字符串中查找子字符串的第一次出现
特点:
- 大小写敏感
- 只返回第一次出现的位置
2.使用示例
#include <stdio.h>
#include <string.h>
int main() {
const char *text = "Hello, World! This is a test string.";
const char *search1 = "World";
const char *search2 = "test";
const char *search3 = "notfound";
// 查找存在的子串
char *result1 = strstr(text, search1);
if (result1 != NULL) {
printf("Found '%s' at position: %ld\n", search1, result1 - text);
printf("Remaining text: %s\n", result1);
}
// 查找另一个存在的子串
char *result2 = strstr(text, search2);
if (result2 != NULL) {
printf("Found '%s' at position: %ld\n", search2, result2 - text);
printf("Remaining text: %s\n", result2);
}
// 查找不存在的子串
char *result3 = strstr(text, search3);
if (result3 == NULL) {
printf("'%s' not found in text.\n", search3);
}
// 查找空字符串
char *result4 = strstr(text, "");
if (result4 != NULL) {
printf("Empty string found at start: %s\n", result4);
}
return 0;
}
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
typedef struct {
time_t timestamp;
char level[10]; // INFO, WARN, ERROR等
char message[256];
} LogEntry;
int parse_log_line(const char *line, LogEntry *entry) {
// 示例日志格式: "[2023-10-25 14:30:25] INFO: User logged in"
// 查找时间戳
char *time_start = strstr(line, "[");
if (time_start == NULL) return 0;
char *time_end = strstr(time_start, "]");
if (time_end == NULL) return 0;
// 提取时间戳字符串
size_t time_len = time_end - time_start - 1;
char time_str[32];
strncpy(time_str, time_start + 1, time_len);
time_str[time_len] = '\0';
// 解析时间戳(简化版本,实际应该使用strptime)
struct tm tm_time;
if (strptime(time_str, "%Y-%m-%d %H:%M:%S", &tm_time) == NULL) {
return 0;
}
entry->timestamp = mktime(&tm_time);
// 查找日志级别
char *level_start = strstr(time_end, "] ");
if (level_start == NULL) return 0;
level_start += 2; // 跳过"] "
char *level_end = strstr(level_start, ":");
if (level_end == NULL) return 0;
// 提取日志级别
size_t level_len = level_end - level_start;
strncpy(entry->level, level_start, level_len);
entry->level[level_len] = '\0';
// 提取消息
char *msg_start = level_end + 2; // 跳过": "
strncpy(entry->message, msg_start, sizeof(entry->message) - 1);
entry->message[sizeof(entry->message) - 1] = '\0';
return 1;
}
void analyze_log(const char *log_file) {
// 模拟日志数据
const char *log_lines[] = {
"[2023-10-25 14:30:25] INFO: User 'alice' logged in",
"[2023-10-25 14:35:10] ERROR: Database connection failed",
"[2023-10-25 14:40:00] WARN: High memory usage detected",
"[2023-10-25 14:45:30] INFO: Backup completed successfully",
NULL
};
printf("Log Analysis:\n");
printf("=============\n");
int error_count = 0;
int warn_count = 0;
int info_count = 0;
for (int i = 0; log_lines[i] != NULL; i++) {
LogEntry entry;
if (parse_log_line(log_lines[i], &entry)) {
printf("[%s] %s: %s\n", entry.level, ctime(&entry.timestamp), entry.message);
// 统计日志级别
if (strstr(entry.level, "ERROR") != NULL) {
error_count++;
} else if (strstr(entry.level, "WARN") != NULL) {
warn_count++;
} else if (strstr(entry.level, "INFO") != NULL) {
info_count++;
}
}
}
printf("\nStatistics:\n");
printf(" INFO: %d\n", info_count);
printf(" WARN: %d\n", warn_count);
printf(" ERROR: %d\n", error_count);
}
int main() {
analyze_log("app.log");
return 0;
}
3.注意事项与常见错误
/****************************** 1.不能传递空指针 ******************************/
// 错误:传递NULL指针
char *result = strstr(NULL, "test"); // 未定义行为,可能崩溃
char *result2 = strstr("test", NULL); // 未定义行为,可能崩溃
// 正确:在使用前检查参数
char *safe_strstr(const char *haystack, const char *needle) {
if (haystack == NULL || needle == NULL) {
return NULL;
}
return strstr(haystack, needle);
}
/****************************** 2.使用返回值前需要检测返回值是否为NULL ******************************/
// 错误:直接使用返回值而不检查NULL
char *text = "Hello";
char *found = strstr(text, "World");
printf("%s\n", found); // 错误!如果未找到,found是NULL
// 正确:总是检查返回值
char *found = strstr(text, "World");
if (found != NULL) {
printf("Found: %s\n", found);
} else {
printf("Not found\n");
}
4.模拟实现
/***************************** 方法一:暴力匹配 *****************************/
int my_strcmp1(const char *str1, const char *str2) {
char *my_strstr1(const char *haystack, const char *needle) {
// 如果needle是空字符串,返回haystack
if (*needle == '\0') {
return (char *)haystack;
}
// 遍历haystack的每个可能起始位置
for (const char *h = haystack; *h != '\0'; h++) {
const char *h_ptr = h;
const char *n_ptr = needle;
// 尝试匹配
while (*h_ptr != '\0' && *n_ptr != '\0' && *h_ptr == *n_ptr) {
h_ptr++;
n_ptr++;
}
// 如果needle的所有字符都匹配了,返回当前位置
if (*n_ptr == '\0') {
return (char *)h;
}
}
// 未找到
return NULL;
}
/***************************** 方法二:KMP算法 *****************************/
// 计算部分匹配表(前缀函数)
void compute_lps(const char *needle, int *lps) {
int len = 0; // 最长前缀后缀的长度
lps[0] = 0; // lps[0]总是0
int i = 1;
while (needle[i] != '\0') {
if (needle[i] == needle[len]) {
len++;
lps[i] = len;
i++;
} else {
if (len != 0) {
len = lps[len - 1];
} else {
lps[i] = 0;
i++;
}
}
}
}
// KMP算法实现
char *my_strstr_kmp(const char *haystack, const char *needle) {
if (*needle == '\0') {
return (char *)haystack;
}
int needle_len = strlen(needle);
int lps[needle_len];
compute_lps(needle, lps);
int i = 0; // haystack的索引
int j = 0; // needle的索引
while (haystack[i] != '\0') {
if (needle[j] == haystack[i]) {
i++;
j++;
}
if (j == needle_len) {
return (char *)(haystack + i - j);
} else if (haystack[i] != '\0' && needle[j] != haystack[i]) {
if (j != 0) {
j = lps[j - 1];
} else {
i++;
}
}
}
return NULL;
}
/***************************** 方法三:模拟库函数实现方法 *****************************/
char *my_strstr3(const char *haystack, const char *needle) {
// 处理空needle
if (*needle == '\0') {
return (char *)haystack;
}
// 遍历haystack
while (*haystack) {
// 如果首字符匹配,尝试完整匹配
if (*haystack == *needle) {
const char *h_ptr = haystack;
const char *n_ptr = needle;
// 逐个字符比较
while (*h_ptr && *n_ptr && *h_ptr == *n_ptr) {
h_ptr++;
n_ptr++;
}
// 如果needle结束,匹配成功
if (*n_ptr == '\0') {
return (char *)haystack;
}
}
haystack++;
}
return NULL;
}
十一、strtok函数的使用(重要,由于可以分割字符串,所以可用于解析命令行)
1.函数原型与特点
原型:
char *strtok(char *str, const char *delimiters);
参数:
str- 要分割的字符串(第一次调用时指定,后续调用传入NULL)
delimiters- 包含所有分隔符的字符串返回值:
返回指向下一个标记(token)的指针
如果没有更多标记,返回
NULL功能: 将字符串分割成一系列标记(子字符串)
特点:
- 会修改原始字符串,用\0替换分隔符
- 对于同一个字符串多次分割,只需要在第一次调用时传入字符串,后续只有传入NULL指针即可
char str[] = "data1,data2,data3"; char *token1 = strtok(str, ","); char *token2 = strtok(NULL, ","); char *token3 = strtok(NULL, ",");- 连续的分隔符被视为单个分隔符
char str[] = "data1,,data2,data3"; char *token = strtok(str, ","); while (token != NULL) { printf("Token: %s\n", token); token = strtok(NULL, ","); } // 输出: // Token: data1 // Token: data2 // Token: data3 // 注意:data1和data2之间的两个逗号被视为一个分隔符- 可以指定多个分隔符
char str[] = "apple; banana,orange\ttgrape"; char *token = strtok(str, ",; \t"); // 分隔符:逗号、分号、空格、制表符 while (token != NULL) { printf("Token: %s\n", token); token = strtok(NULL, ",; \t"); } // 输出: // Token: apple // Token: banana // Token: orange // Token: grape
2.使用示例
#include <stdio.h>
#include <string.h>
int main() {
char str[] = "apple,banana,orange,grape";
const char *delimiters = ",";
// 第一次调用,传入要分割的字符串
char *token = strtok(str, delimiters);
// 继续分割,直到返回NULL
while (token != NULL) {
printf("Token: %s\n", token);
token = strtok(NULL, delimiters); // 后续调用传入NULL
}
return 0;
}
/******************* 输出结果 *******************/
Token: apple
Token: banana
Token: orange
Token: grape
3.注意事项与常见错误
/****************************** 1.不可用于分割常量字符串 ******************************/
// 错误:试图分割字符串字面量(只读内存)
char *str = "apple,banana"; // 字符串字面量,存储在只读内存
char *token = strtok(str, ","); // 运行时错误!试图修改只读内存
// 正确:使用字符数组
char str[] = "apple,banana"; // 字符数组,存储在可修改的栈内存
char *token = strtok(str, ","); // 正确
/****************************** 2.如果需要保留原始字符串,先复制一份 ******************************/
// strtok会修改原始字符串
char original[] = "one-two-three";
printf("Before: %s\n", original); // 输出: one-two-three
char *token = strtok(original, "-");
while (token != NULL) {
printf("Token: %s\n", token);
token = strtok(NULL, "-");
}
printf("After: %s\n", original); // 输出: one(不是原来的字符串了!)
// 如果需要保留原始字符串,先复制一份
char backup[50];
strcpy(backup, original); // 先复制再处理
/****************************** 3.分隔符字符串中的字符是集合,不是子字符串******************************/
// 分隔符字符串中的字符是集合,不是子字符串
char str[] = "a-b-c";
char *token = strtok(str, "-+"); // 分隔符是'-'或'+',不是子字符串"-+"
// 正确:会按'-'分割
// 错误理解:以为是分割子字符串"-+"
// 正确理解:任何'-'或'+'字符都是分隔符
/****************************** 4.无法处理空标记 ******************************/
// 如果字符串以分隔符开头,第一个标记可能是空字符串
// 但strtok会跳过开头的分隔符,所以不会返回空标记
char str[] = ",apple,banana";
char *token = strtok(str, ",");
while (token != NULL) {
printf("Token: '%s'\n", token);
token = strtok(NULL, ",");
}
// 输出:
// Token: 'apple'
// Token: 'banana'
// 注意:没有空字符串标记
十二、strerror函数的使用
1.函数原型与特点
原型:
char *strerror(int errnum);
参数:
errnum- 错误号(通常来自errno变量)返回值: 指向描述错误信息的字符串的指针
功能: 将错误号转换为可读的错误描述字符串
特点:
- 返回值指向静态内存,不可被修改
- 对于未知的错误码,strerror可能返回"Unknown error"或类似信息
// 对于未知的错误码,strerror可能返回"Unknown error"或类似信息 printf("Error 9999: %s\n", strerror(9999)); // 可能输出: Unknown error
2.使用示例
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
int main() {
// 示例1: 基本用法
for (int i = 0; i < 5; i++) {
printf("Error %d: %s\n", i, strerror(i));
}
// 示例2: 与实际错误结合使用
FILE *file = fopen("non_existent_file.txt", "r");
if (file == NULL) {
printf("Failed to open file: %s (errno=%d)\n",
strerror(errno), errno);
}
// 示例3: 常见的标准错误
printf("\nCommon standard errors:\n");
printf("EINVAL (%d): %s\n", EINVAL, strerror(EINVAL));
printf("ENOENT (%d): %s\n", ENOENT, strerror(ENOENT));
printf("EACCES (%d): %s\n", EACCES, strerror(EACCES));
printf("ENOMEM (%d): %s\n", ENOMEM, strerror(ENOMEM));
return 0;
}
3.注意事项与常见错误
/****************************** 1.返回值为常量字符串,不可被修改 ******************************/
// strerror返回的字符串是只读的
char *error_msg = strerror(EINVAL);
// 错误:试图修改字符串
// error_msg[0] = 'X'; // 未定义行为!可能导致程序崩溃
// 正确:如果需要修改,先复制一份
char my_copy[100];
strncpy(my_copy, error_msg, sizeof(my_copy) - 1);
my_copy[sizeof(my_copy) - 1] = '\0';
// 现在可以安全地修改my_copy
更多推荐



所有评论(0)