目录

一、字符分类函数

二、字符转换函数

三、strlen的使用和模拟实现(重要)

1.函数原型与特点

2.使用示例

3.注意事项与常见错误

4.模拟实现

5.与sizeof的区别

四、strcpy的使用和模拟实现(重要)

1.函数原型与特点

2.使用示例

3.注意事项与常见错误

4.模拟实现

五、strcat的使用和模拟实现(重要)

1.函数原型与特点

2.使用示例

3.注意事项与常见错误

4.模拟实现

六、strcmp的使用和模拟实现(重要)

1.函数原型与特点

2.使用示例

3.注意事项与常见错误

4.模拟实现

七、strncpy函数的使用(指定字符个数拷贝)

1.函数原型与特点

2.使用示例

3.注意事项与常见错误

4.与strcpy、memcpy的区别

八、strncat函数的使用(指定字符个数连接)

1.函数原型与特点

2.使用示例

3.注意事项与常见错误

4.与strcat、snprintf的区别

九、strncmp函数的使用(指定字符个数比较)

1.函数原型与特点

2.使用示例

3.注意事项与常见错误

4.与strcmp、memcmp的区别

十、strstr的使用和模拟实现(重要,由于可以查找字符串,所以可以用于日志)

1.函数原型与特点

2.使用示例

3.注意事项与常见错误

4.模拟实现

十一、strtok函数的使用(重要,由于可以分割字符串,所以可用于解析命令行)

1.函数原型与特点

2.使用示例

3.注意事项与常见错误

十二、strerror函数的使用

1.函数原型与特点

2.使用示例

3.注意事项与常见错误


一、字符分类函数

函数名 功能描述 示例字符
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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐