C中字符串处理学习笔记

C中字符串处理学习笔记

C语言中,字符串操作是非常常见的任务,标准库 <string.h> 提供了一系列的函数来处理字符串:

函数原型功能描述
size_t strlen(const char *s);返回字符串 s 的长度,不包括终止符 \0
size_t strnlen(const char *s, size_t maxlen);返回字符串 s 的长度,但不超过 maxlen,不包括终止符 \0
char *strcpy(char *dest, const char *src);src 字符串复制到 dest,包括终止符。
char *strncpy(char *dest, const char *src, size_t n);src 的前 n 个字符复制到 dest,如果 src 的长度小于 n,则 dest 的剩余部分用 \0 填充。
size_t strlcpy(char *dst, const char *src, size_t siz);安全地将 src 复制到 dst,最多复制 siz-1 个字符,然后添加 \0
char *strcat(char *dest, const char *src);src 追加到 dest 的末尾,覆盖 dest 的终止符。
char *strncat(char *dest, const char *src, size_t n);src 的前 n 个字符追加到 dest 的末尾。
size_t strlcat(char *dst, const char *src, size_t siz);安全地将 src 追加到 dst 的末尾,最多追加 siz-sizeof(dst) 个字符,然后添加 \0
int sprintf(char *str, const char *format, …);将格式化字符串写入 str
int snprintf(char *str, size_t size, const char *format, …);将格式化字符串写入 str,最多写入 size-1 个字符,然后添加 \0
int asprintf(char **ret, const char *format, …);将格式化字符串写入动态分配的内存,并将指针存储在 ret 中。
int vsnprintf(char *str, size_t size, const char *format, va_list ap);将格式化字符串写入 str,使用可变参数列表 ap
int vsprintf(char *str, const char *format, va_list ap);将格式化字符串写入 str,使用可变参数列表 ap
int vfprintf(FILE *stream, const char *format, va_list ap);将格式化字符串写入文件流 stream,使用可变参数列表 ap
void *memcpy(void *dest, const void *src, size_t n);src 的前 n 个字节复制到 dest
void *memmove(void *dest, const void *src, size_t n);src 的前 n 个字节移动到 dest,可以处理重叠的区域。
void *memset(void *ptr, int value, size_t num);ptr 的前 num 个字节设置为 value
char *strdup(const char *s);复制 s 到动态分配的内存,并返回指向新复制字符串的指针。
char *strndup(const char *s, size_t n);复制 s 的前 n 个字符到动态分配的内存,并返回指向新复制字符串的指针。
char *strtok(char *str, const char *delim);将字符串 str 分割成标记,使用 delim 作为分隔符。
int strcmp(const char *s1, const char *s2);比较字符串 s1s2
int strncmp(const char *s1, const char *s2, size_t n);比较字符串 s1s2 的前 n 个字符。
char *strstr(const char *haystack, const char *needle);haystack 中查找 needle 的第一次出现。
char *strchr(const char *s, int c);在字符串 s 中查找字符 c 的第一次出现。
char *strrchr(const char *s, int c);在字符串 s 中查找字符 c 的最后一次出现。
size_t strspn(const char *s, const char *accept);返回 s 开头连续的字符与 accept 中字符相匹配的最长长度。
size_t strcspn(const char *s, const char *reject);返回 s 开头连续的字符与 reject 中字符都不匹配的最长长度。
int memcmp(const void *s1, const void *s2, size_t n);比较 s1s2 的前 n 个字节。

1. 字符串长度相关函数

strlenstrnlen 是C语言中用于处理字符串长度的两个函数,它们在功能上有所不同,适用于不同的场景。

1.1 strlen函数

strlen 是C标准库中用于计算字符串长度的函数,它返回一个以空字符(‘\0’)终止的字符串的长度,不包括终止符本身。这个函数定义在 <string.h> 头文件中。

函数原型:

size_t strlen(const char *s);
  • s: 是指向要测量长度的字符串的指针。
  • 返回值:返回字符串的长度(不包括终止符)。

工作原理:

strlen 从给定的指针开始遍历,逐个字符地读取,直到遇到第一个空字符(‘\0’)。一旦遇到 ‘\0’,它就停止计数并返回之前已计数的字符数。

示例:

#include <stdio.h>
#include <string.h>

int main() {
    char str[] = "Hello, World!";
    printf("Length of string: %zu\n", strlen(str));
    return 0;
}

/* 输出结果:
*  Length of string: 13 */

注意事项:

  • 字符串必须正确终止strlen 函数依赖于字符串的正确终止符 \0。如果字符串未被正确终止,strlen 将继续遍历内存直到找到下一个 \0,这可能导致未定义行为或程序崩溃。
  • 避免无限循环:如果传入 strlen 的字符串没有 \0 终止符,函数将无限循环直到遇到内存中的某个随机 \0 字符或超出程序的地址空间,这可能导致程序挂起或崩溃。
  • 指针的有效性:确保传递给 strlen 的指针 s 是有效的,指向的是分配给字符串的内存区域。如果 sNULL 或者指向未分配的内存,将导致运行时错误或程序崩溃。
  • 缓冲区大小:在使用 strlen 计算字符串长度后,如果计划使用这个长度,比如在 malloccalloc 中申请内存,确保考虑到终止符的额外空间。
  • 性能考量strlen 的时间复杂度为 O(n),其中 n 是字符串的长度。在性能敏感的应用中,如果字符串长度频繁变化,可能需要考虑缓存字符串长度以避免重复计算。
  • 使用场景:在动态分配字符串或需要知道字符串确切长度的场景下,strlen 是一个必要的工具。但在处理可能未正确终止的字符串时,应考虑使用更安全的函数如 strnlen

1.2 strnlen函数

strnlen 不是C标准库的一部分,但它在某些实现中(如GNU libc)可用,用于计算字符串的长度,但与 strlen 不同的是,它允许你指定一个最大扫描的字符数,从而避免了潜在的无限循环或超出预期的扫描范围。

函数原型:

size_t strnlen(const char *s, size_t maxlen);
  • s: 是指向要测量长度的字符串的指针。
  • maxlen: 是一个指定的最大扫描字符数。
  • 返回值:返回字符串的长度(不包括终止符),但如果在 maxlen 字符内没有找到终止符,它将返回 maxlen

工作原理:

strnlenstrlen 类似,但它在达到 maxlen 字符或遇到 ‘\0’ 时停止计数。如果在 maxlen 字符内找到了 ‘\0’,它将返回到达终止符之前的字符数。如果未在 maxlen 字符内找到终止符,它将返回 maxlen,这通常意味着字符串未被正确终止或超出了期望的长度。

#include <stdio.h>
#include <string.h>

int main() {
    char str[] = "Hello, World!";
    printf("Length of string with maxlen=10: %zu\n", strnlen(str, 10));
    printf("Length of string with maxlen=20: %zu\n", strnlen(str, 20));
    return 0;
}

/* 输出结果:
*  Length of string with maxlen=10: 10
*  Length of string with maxlen=20: 13 */

注意事项:

  • 边界检查strnlen 可以帮助你检查字符串是否超过预期的长度。这对于处理可能来自不受信任来源的数据尤其重要。
  • 未终止的字符串:如果字符串未被正确终止,strnlen 将返回 maxlen,这表明字符串可能超过了预期的长度或没有正确终止。这种情况下,后续的字符串操作函数(如 strcpy, strcat 等)可能会导致缓冲区溢出。
  • 安全编码实践:使用 strnlen 可以作为一种安全措施,避免在处理可能未正确终止的字符串时出现无限循环。但是,这并不完全消除所有安全风险,因此还需要结合其他安全措施,如验证输入和使用更安全的字符串操作函数。
  • 兼容性strnlen 不是C标准库的一部分,所以在某些环境下可能不可用。在跨平台开发时,你可能需要提供自己的实现或使用其他库函数作为替代。
  • 性能考虑:在大多数情况下,strnlen 的性能与 strlen 类似,但在最坏的情况下(即字符串长度接近 maxlen 且未找到终止符),strnlen 可能会稍微慢一些,因为它必须扫描整个 maxlen 范围。

2. 字符串复制、设置相关函数

2.1 strcpy函数

strcpy 是C语言中用于字符串复制的一个基本函数,用于将一个字符串的内容复制到另一个字符串中。

函数原型:

char *strcpy(char *dest, const char *src);
  • dest: 是一个指向目标字符串的指针。目标字符串必须有足够的空间来存储源字符串和终止符。
  • src: 是一个指向源字符串的常量指针。

工作原理:

strcpy 函数从 src 指向的字符串开始,逐个字符地复制到 dest 指向的字符串中,直到遇到源字符串的终止符(\0)。在复制过程中,终止符也被复制,以确保目标字符串是正确终止的。

示例代码:

#include <stdio.h>
#include <string.h>

int main() {
    char source[20] = "Hello, World!";
    char destination[20];

    // 使用strcpy复制字符串
    strcpy(destination, source);

    printf("Original String: %s\n", source);
    printf("Copied String: %s\n", destination);

    return 0;
}

/* 输出结果:
*  Original String: Hello, World!
*  Copied String: Hello, World! */

注意事项:

  • 缓冲区溢出:确保目标字符串 dest 的缓冲区足够大,能够容纳源字符串 src 的所有字符加上终止符 \0。如果目标缓冲区太小,strcpy 会导致缓冲区溢出,这可能引发程序崩溃或安全漏洞。
  • 源字符串的终止strcpy 依赖于源字符串 src 的正确终止。如果源字符串没有正确终止,strcpy 将继续复制直到遇到下一个 \0 字符,这可能导致未定义的行为。
  • 目标字符串初始化:在使用 strcpy 之前,确保目标字符串 dest 已经被适当初始化,例如,通过分配足够的内存或将其设置为一个足够大的静态数组。
  • 目标字符串的使用:使用 strcpy 后,目标字符串 dest 将包含源字符串 src 的内容,包括终止符 \0
  • 安全替代方案:如果目标缓冲区的大小有限或不确定源字符串的长度,考虑使用 strncpystrlcpy 等更安全的函数,它们允许你指定复制的最大字符数,从而避免缓冲区溢出。
  • 字符串复制后的处理:在复制字符串后,如果需要进一步处理目标字符串,例如追加更多文本,确保再次检查缓冲区的大小,以避免后续的缓冲区溢出。

2.2 strncpy函数

strncpy 函数是C语言中用于字符串操作的一个重要函数,它允许用户指定复制的字符数,这在处理固定长度的字符串或避免缓冲区溢出时非常有用。

函数原型:

char *strncpy(char *dest, const char *src, size_t n);
  • dest: 目标字符串的指针,即复制后字符串存放的地方。
  • src: 源字符串的指针,即待复制的字符串。
  • n: 要复制的字符数,包括源字符串的任何字符,但不自动包括终止符 \0

工作原理:

strncpy 函数会从 src 中复制最多 n 个字符到 dest。如果 src 的长度小于 nstrncpy 将会复制所有字符,包括终止符 \0,并在剩余位置填充 \0 直至 n。然而,如果 src 的长度大于或等于 nstrncpy 仅复制 n 个字符,不会自动在 dest 的末尾添加终止符。

示例代码:

#include <stdio.h>
#include <string.h>

int main() {
    char dest[10];
    const char *src = "Hello, World!";
    
    // 尝试复制10个字符
    strncpy(dest, src, 10);
    printf("Result: %s\n", dest); // 注意:可能输出不完整或乱码,因为没有自动添加终止符

    // 正确的使用:手动添加终止符
    strncpy(dest, src, 9); // 复制前9个字符
    dest[9] = '\0';        // 明确添加终止符
    printf("Correct Result: %s\n", dest); // 输出 "Hello, Wo"

    return 0;
}

/* 输出结果:
*  Result: Hello, Wor
*  Correct Result: Hello, Wo */

注意事项:

  • 终止符的处理strncpy 不会自动在目标字符串的末尾添加终止符,如果 n 大于或等于源字符串的长度。确保在复制后检查并添加终止符,以避免潜在的未定义行为。
  • 缓冲区溢出的避免strncpy 的一个主要优点是你可以指定复制的最大字符数,这有助于避免缓冲区溢出。但是,如果目标缓冲区大小小于 n,仍然可能导致溢出。
  • 源字符串长度的考虑:如果源字符串的长度小于 nstrncpy 会复制所有字符,并在目标字符串中剩余的位置填充 \0。这可能不是所有情况下的预期行为。
  • 安全替代品strlcpy 是一个更安全的替代品,它保证目标字符串总是被正确终止,同时返回源字符串的实际长度。

2.3 strlcpy函数

strlcpy 函数是非标准的字符串复制函数,尽管如此,它在许多环境中(特别是GNU和BSD系统中)被广泛使用,因为它提供了一种安全的方式来复制字符串,避免了缓冲区溢出等问题。

函数原型

strlcpy 的函数原型通常如下所示,但请注意,它并非C标准库的一部分,而是某些实现(如GNU libc或FreeBSD的libc)的扩展:

size_t strlcpy(char *dest, const char *src, size_t size);
  • dest: 目标字符串的指针,这里将存放复制后的字符串。
  • src: 源字符串的指针,这是要复制的字符串。
  • size: 目标缓冲区的大小(以字节为单位),包括终止符。

工作原理:

strlcpy 会尝试将 src 中的字符串复制到 dest 中,但不会超过 size - 1 的字节,以确保 dest 的最后一个字节可以用于存储终止符 \0。如果 src 的长度超过 size - 1,strlcpy 会截断字符串并确保 dest\0 正确终止。函数返回值是 src 的实际长度(不包括终止符),即使它被截断了。

示例代码:

#include <stdio.h>
#include <string.h> // For strlcpy in some implementations
#include <sys/types.h> // For strlcpy in BSD-based systems

int main() {
    char dest[10]; // 目标缓冲区,包含终止符的位置
    const char *src1 = "Hello";
    const char *src2 = "Hello, World!";

    // 复制较短的字符串
    size_t len1 = strlcpy(dest, src1, sizeof(dest));
    printf("Source: '%s', Length: %zu, Copied: '%s'\n", src1, len1, dest);

    // 清空目标缓冲区
    dest[0] = '\0';

    // 尝试复制较长的字符串
    size_t len2 = strlcpy(dest, src2, sizeof(dest));
    printf("Source: '%s', Length: %zu, Copied: '%s'\n", src2, len2, dest);

    return 0;
}

/* 输出结果:
*  Source: 'Hello', Length: 5, Copied: 'Hello'
*  Source: 'Hello, World!', Length: 13, Copied: 'Hello, W' */

注意事项:

  • 非标准库函数strlcpy 并不是C标准库的一部分,因此在某些编译器或平台上可能不可用。在使用时,请确保你的环境支持这个函数,或准备好一个兼容的替代实现。
  • 安全复制strlcpy 的设计目的是为了安全地复制字符串,避免缓冲区溢出。它会确保目标缓冲区总是被正确终止,即使源字符串比目标缓冲区大。
  • 返回值strlcpy 的返回值是源字符串的实际长度,不包括终止符。这可以用来判断是否发生了截断,以及源字符串的真实长度。
  • 缓冲区大小:在调用 strlcpy 时,size 参数应当包括目标缓冲区的终止符位置。也就是说,如果你有一个大小为 N 的字符数组,那么 size 应该是 N,这样 strlcpy 才能正确地预留一个字节用于终止符。
  • 截断处理:如果源字符串比目标缓冲区大,strlcpy 会进行截断,但仍会保证目标字符串被正确终止。这一点在处理可能未知长度的字符串时非常重要。

2.4 strcat函数

strcat 是C语言中用于将一个字符串连接到另一个字符串末尾的函数。

函数原型:

char *strcat(char *dest, const char *src);
  • dest: 目标字符串的指针,此字符串将被追加。
  • src: 源字符串的指针,此字符串将被追加到目标字符串的末尾。

工作原理:

strcat 函数查找目标字符串 dest 的终止符 \0,然后从该点开始复制源字符串 src,直到遇到源字符串的终止符 \0。结果是 dest 字符串包含了 src 字符串的内容,位于 dest 原有内容的后面,最终的字符串由单个终止符 \0 结束。

示例代码:

#include <stdio.h>
#include <string.h>

int main() {
    char dest[50] = "Hello, ";
    const char *src = "World!";

    // 使用strcat连接字符串
    strcat(dest, src);
    printf("Concatenated string: %s\n", dest);

    return 0;
}

/* 输出结果:
*  Concatenated string: Hello, World! */

注意事项:

  • 缓冲区溢出:使用 strcat 时,确保目标字符串 dest 的缓冲区足够大,能够容纳源字符串 src 的所有字符,加上原有的字符和终止符。如果目标缓冲区太小,strcat 将导致缓冲区溢出,这可能引起程序崩溃或安全漏洞。
  • 目标字符串必须已初始化strcat 函数假设目标字符串 dest 已经包含了一个终止符 \0。如果目标字符串未被正确初始化或没有终止符,strcat 将无法正确确定复制的起点,从而可能导致未定义行为。
  • 源字符串的使用strcat 不改变源字符串 src,只是将它追加到目标字符串 dest 的末尾。
  • 安全替代品:对于已知或可能未知长度的字符串,使用 strncatstrlcat 可以提供更安全的字符串连接,因为这些函数允许你指定目标缓冲区的大小,从而避免缓冲区溢出。

2.5 strncat函数

strncat 函数在C语言中用于安全地将一个字符串的部分内容连接到另一个字符串的末尾,允许指定要连接的字符数量,这有助于避免缓冲区溢出的问题。

函数原型:

char *strncat(char *dest, const char *src, size_t n);
  • dest: 目标字符串的指针,此字符串将被追加。
  • src: 源字符串的指针,从此字符串中复制字符到目标字符串。
  • n: 要复制的字符数,包括源字符串中的字符,但不包括终止符。

工作原理:

strncat 函数查找目标字符串 dest 的终止符 \0,然后从该点开始复制源字符串 src 的最多 n 个字符,直到遇到源字符串的终止符 \0 或达到 n 的限制为止。结果是 dest 字符串包含了 src 字符串的部分内容,位于 dest 原有内容的后面,最终的字符串由单个终止符 \0 结束。

示例代码:

#include <stdio.h>
#include <string.h>

int main() {
    char dest[50] = "Hello, ";
    const char *src = "World! Welcome to programming.";

    // 使用strncat连接字符串,限制为10个字符
    strncat(dest, src, 10);
    printf("Concatenated string: %s\n", dest);

    return 0;
}

/* 输出结果:
*  Concatenated string: Hello, World! Wel */

注意事项:

  • 缓冲区溢出:尽管 strncat 提供了字符数量的限制,你仍然需要确保目标字符串 dest 的缓冲区足够大,能够容纳源字符串 src 的指定字符数量加上原有的字符和终止符。如果目标缓冲区太小,strncat 仍然可能导致缓冲区溢出。
  • 目标字符串必须已初始化strncat 函数假设目标字符串 dest 已经包含了一个终止符 \0。如果目标字符串未被正确初始化或没有终止符,strncat 将无法正确确定复制的起点,从而可能导致未定义行为。
  • 源字符串的使用strncat 不改变源字符串 src,只是将它的一部分追加到目标字符串 dest 的末尾。
  • 终止符的处理:即使 n 个字符不足以到达源字符串的终止符,strncat 也会在目标字符串的末尾添加一个终止符,以确保结果字符串是有效的。
  • 安全性和效率strncatstrcat 更安全,因为它限制了追加的字符数量,从而减少了缓冲区溢出的风险。然而,过度限制 n 可能会导致源字符串被截断,这可能不是预期的行为。

2.6 strlcat函数

strlcat 是一个非标准但非常实用的函数,用于安全地将一个字符串追加到另一个字符串的末尾,同时确保目标字符串不会超出其分配的大小。

函数原型:

size_t strlcat(char *dest, const char *src, size_t siz);
  • dest: 目标字符串的指针,这是接收追加字符串的缓冲区。
  • src: 源字符串的指针,这是要追加到目标字符串的字符串。
  • siz: 目标缓冲区的大小(以字节为单位),包括终止符。

工作原理:

strlcat 函数计算 dest 当前的长度,然后尝试将 src 中的字符串追加到 dest 的末尾,但不会超过 siz - 1 的字节,以确保 dest 的最后一个字节可以用于存储终止符。如果 src 的长度加上 dest 当前的长度超过 siz - 1,strlcat 会截断 src,以适应目标缓冲区的大小,并确保 dest\0 正确终止。函数返回值是追加操作后 dest 的总长度(不包括终止符)。

示例代码:

#include <stdio.h>
#include <string.h> // For strlcat in some implementations
#include <sys/types.h> // For strlcat in BSD-based systems

int main() {
    char dest[20] = "Hello, ";
    const char *src1 = "World!";
    const char *src2 = ", how are you today?";

    // 追加较短的字符串
    size_t len1 = strlcat(dest, src1, sizeof(dest));
    printf("Destination after appending '%s': '%s', Total length: %zu\n", src1, dest, len1);

    // 清空目标缓冲区
    dest[0] = '\0';

    // 追加较长的字符串
    len1 = strlcat(dest, "Hello, ", sizeof(dest));
    len1 += strlcat(dest + len1, src2, sizeof(dest) - len1);
    printf("Destination after appending '%s': '%s', Total length: %zu\n", src2, dest, len1);

    return 0;
}

/* 输出结果:
*  Destination after appending 'World!': 'Hello, World!', Total length: 13
*  Destination after appending ', how are you today?': 'Hello, , how are you to', Total length: 23 */

即使追加的字符串 "how are you today?" 的长度加上目标缓冲区中已有字符串的长度超过了目标缓冲区的大小,strlcat 仍然能够正确地追加字符串并确保目标缓冲区以终止符正确终止。

注意事项:

  • 非标准库函数:与 strlcpy 一样,strlcat 并不是C标准库的一部分,因此在某些编译器或平台上可能不可用。在使用时,请确保你的环境支持这个函数,或准备好一个兼容的替代实现。
  • 安全追加strlcat 的设计目的是为了安全地追加字符串,避免缓冲区溢出。它会确保目标缓冲区总是被正确终止,即使追加的字符串使目标缓冲区满。
  • 返回值strlcat 的返回值是追加操作后目标字符串的总长度,不包括终止符。这可以用来判断是否发生了截断,以及目标字符串的真实长度。
  • 缓冲区大小:在调用 strlcat 时,siz 参数应当包括目标缓冲区的终止符位置。也就是说,如果你有一个大小为 N 的字符数组,那么 siz 应该是 N,这样 strlcat 才能正确地预留一个字节用于终止符。
  • 截断处理:如果追加的字符串加上目标字符串的当前长度超过目标缓冲区的大小,strlcat 会进行截断,但仍会保证目标字符串被正确终止。

2.7 sprintf函数

sprintf 函数在C语言中用于将格式化的字符串写入到一个字符数组中,类似于 printf 函数,但输出定向到一个缓冲区而不是标准输出。

函数原型:

int sprintf(char *str, const char *format, ...);
  • str: 指向字符数组的指针,格式化后的字符串将被写入到这个数组中。
  • format: 格式字符串,用于控制输出的格式。
  • ...: 可变参数列表,提供格式字符串中占位符所需的实际数据。

工作原理:

sprintf 函数按照 format 字符串中指定的格式,将后续参数列表中的数据格式化,并将结果写入到 str 指向的字符数组中。与 printf 类似,format 字符串可以包含各种格式化指令,如 %d(十进制整数)、%s(字符串)等。但是,与 printf 不同的是,sprintf 将输出存储在一个字符数组中,而不是输出到标准输出设备。

示例代码:

#include <stdio.h>

int main() {
    char buffer[50];
    int number = 42;
    double pi = 3.14159;

    // 使用sprintf格式化字符串
    int result = sprintf(buffer, "The answer is %d and pi is %.2f", number, pi);
    if (result > 0 && result < sizeof(buffer)) {
        printf("Formatted string: %s\n", buffer);
    } else {
        printf("Buffer was too small or an error occurred.\n");
    }

    return 0;
}

/* 输出结果:
*  Formatted string: The answer is 42 and pi is 3.14 */

注意事项:

  • 缓冲区溢出:使用 sprintf 时,必须确保 str 指向的字符数组足够大,能够容纳格式化后的字符串。如果数组太小,sprintf 将导致缓冲区溢出,这可能引发程序崩溃或安全漏洞。
  • 格式化规则format 字符串中的格式化指令必须与参数列表中的数据类型相匹配。否则,sprintf 的行为可能是未定义的,可能导致数据损坏或程序异常。
  • 返回值sprintf 的返回值是格式化后的字符串的长度(不包括终止符),这可以用来检查是否发生了缓冲区溢出。
  • 安全替代品:为了防止缓冲区溢出,推荐使用 snprintf 函数,它允许你指定目标缓冲区的大小,从而避免溢出问题。
  • 终止符sprintf 会在格式化后的字符串末尾自动添加终止符,因此 str 指向的字符数组的大小应该比预计的字符串长度多1个字节,以容纳终止符。

2.8 snprintf函数

snprintf 函数是C语言中用于格式化字符串并安全地写入到指定大小的缓冲区中的一个强大工具。

函数原型:

int snprintf(char *str, size_t size, const char *format, ...);
  • str: 目标字符串的指针,也就是格式化后的字符串将被写入的缓冲区。
  • size: 目标缓冲区的大小(以字节为单位),包括终止符。
  • format: 一个格式字符串,它描述了如何格式化输出。
  • ...: 可变参数列表,包含了 format 字符串中占位符所对应的具体值。

工作原理:

snprintf 函数根据 format 规定的格式和后续参数列表中的值生成一个字符串,并将其安全地写入到 str 所指向的缓冲区中。size 参数确保写入的字符串不会超出缓冲区的大小,避免了缓冲区溢出的可能。如果格式化后的字符串长度超过 size - 1,snprintf 将会被截断以适应缓冲区,并确保字符串以 \0 正确终止。

示例代码:

#include <stdio.h>

int main() {
    char buffer[50];
    int number = 42;
    double pi = 3.14159;

    // 使用snprintf格式化字符串
    int result = snprintf(buffer, sizeof(buffer), "The answer is %d and pi is %.2f", number, pi);
    if (result >= 0 && result < sizeof(buffer)) {
        printf("Formatted string: %s\n", buffer);
    } else {
        printf("Buffer was too small.\n");
    }

    return 0;
}

/* 输出结果:
*  Formatted string: The answer is 42 and pi is 3.14 */

注意事项:

  • 缓冲区大小:确保 size 参数足够大,以容纳格式化后的字符串。如果 size 太小,snprintf 会将字符串截断,但仍然会以 \0 终止,以避免缓冲区溢出。
  • 返回值snprintf 的返回值是格式化后的字符串的理论长度(不包括终止符)。如果这个长度大于或等于 size,说明字符串被截断了。
  • 安全和效率:使用 snprintf 可以避免常见的格式化字符串漏洞,因为它限制了写入的目标缓冲区的大小。然而,如果格式化字符串或参数列表中有错误,仍然可能导致未定义行为。
  • sprintf 的区别snprintfsprintf 类似,但 sprintf 不接受 size 参数,因此它不会检查缓冲区溢出,使用时需要格外小心。
  • 类型安全:在 format 字符串中,确保使用正确的转换说明符与参数列表中的数据类型相匹配,以避免类型不匹配导致的问题。

2.9 asprintf函数

asprintf 函数类似于 sprintf,但它的独特之处在于它动态地分配内存来存储格式化后的字符串,而不是使用预先分配的缓冲区。这使得 asprintf 成为一个在不知道所需缓冲区大小时格式化字符串的有用工具。

函数原型:

int asprintf(char **ret, const char *format, ...);
  • ret: 一个指向 char * 的指针,asprintf 会将指向新分配的、格式化后的字符串的指针存储在这里。
  • format: 格式字符串,用于控制输出的格式。
  • ...: 可变参数列表,提供格式字符串中占位符所需的实际数据。

工作原理:

asprintf 根据 format 和后续参数列表中的数据格式化一个字符串,然后动态地分配足够的内存来存储这个字符串(包括终止符),并将指向这块内存的指针通过 ret 参数返回。如果格式化成功,asprintf 返回字符串的长度(不包括终止符);如果失败(例如,内存分配失败),则返回一个负数。

示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>

int main() {
    char *formatted_string;
    int number = 42;
    double pi = 3.14159;

    // 使用asprintf格式化字符串
    int result = asprintf(&formatted_string, "The answer is %d and pi is %.2f", number, pi);
    if (result > 0) {
        printf("Formatted string: %s\n", formatted_string);
    } else {
        fprintf(stderr, "Failed to format the string.\n");
    }

    // 释放分配的内存
    free(formatted_string);

    return 0;
}

/* 输出结果:
*  Formatted string: The answer is 42 and pi is 0.00 */

注意事项:

  • 内存管理:使用 asprintf 格式化字符串后,必须使用 free 函数释放分配的内存。忘记释放内存会导致内存泄漏。
  • 错误处理:如果 asprintf 失败,它返回一个负数。通常,这发生在内存分配失败时。因此,在使用 asprintf 之后,应该检查返回值是否为正数。
  • 可移植性asprintf 不是C语言标准库的一部分,而是在某些实现中(如GNU libc)提供的扩展。在使用 asprintf 时,需要确保你的编译器和库支持它,或者准备一个备用方案。
  • 类型安全:与 sprintf 类似,format 字符串中的格式化指令必须与参数列表中的数据类型相匹配。否则,asprintf 的行为可能是未定义的。
  • 线程安全:在多线程环境中,asprintf 的行为可能不是线程安全的,具体取决于底层的内存分配函数。如果在多线程程序中使用 asprintf,可能需要采取额外的同步措施。

2.10 vsnprintf函数

vsnprintf 函数是C语言中的一个用于格式化字符串的函数,它是 snprintf 的变体,专门用于处理可变参数列表。这使得 vsnprintf 成为处理格式化字符串时更加灵活和安全的工具。

函数原型:

int vsnprintf(char *str, size_t size, const char *format, va_list ap);
  • str: 目标字符串的指针,格式化后的字符串将被写入这里。
  • size: 目标缓冲区的大小(以字节为单位),包括终止符。
  • format: 控制输出格式的格式字符串。
  • ap: 一个 va_list 类型的参数,用于存储可变参数列表。

工作原理:

vsnprintf 函数将 format 字符串中的占位符替换为 ap 参数列表中提供的值,并将结果写入到 str 指向的缓冲区中。与 snprintf 类似,vsnprintf 会确保写入的字符串不会超出缓冲区的大小,从而避免了缓冲区溢出的风险。如果格式化后的字符串长度超过 size - 1,vsnprintf 会将字符串截断,但会确保字符串以终止符正确终止。

示例代码:

#include <stdio.h>
#include <string.h>
#include <stdarg.h>

int main() {
    char buffer[50];
    int number = 42;
    double pi = 3.14159;
    const char* greeting = "Hello";

    // 初始化可变参数列表
    va_list args;
    va_start(args, buffer);
    va_copy(args, args); // 复制参数列表,用于两次调用

    // 使用vsnprintf格式化字符串
    int result = vsnprintf(buffer, sizeof(buffer), "%s, the answer is %d and pi is %.2f", args);
    if (result >= 0 && result < sizeof(buffer)) {
        printf("Formatted string: %s\n", buffer);
    } else {
        printf("Buffer was too small or an error occurred.\n");
    }

    // 第二次调用,使用相同的参数列表
    char second_buffer[50];
    result = vsnprintf(second_buffer, sizeof(second_buffer), "%s, the answer is %d and pi is %.2f", args);
    if (result >= 0 && result < sizeof(second_buffer)) {
        printf("Second formatted string: %s\n", second_buffer);
    } else {
        printf("Second buffer was too small or an error occurred.\n");
    }

    // 清理可变参数列表
    va_end(args);

    return 0;
}

/* 输出结果:
*  Formatted string: Hello, the answer is 42 and pi is 3.14
*  Second formatted string: Hello, the answer is 42 and pi is 3.14 */

在这段代码中,使用了 vsnprintf 来格式化一个字符串,该字符串包含了字符串 greeting、整数 number 和浮点数 pi。首先初始化了可变参数列表 args,并使用 va_copy 创建了列表的一个副本,以便可以多次调用 vsnprintf 而不会破坏原始参数列表。

两次调用了 vsnprintf,第一次将格式化的字符串放入 buffer,第二次放入 second_buffer。每次调用后,我们检查了 vsnprintf 的返回值,以确保没有发生缓冲区溢出或错误。

最后,使用 va_end 来清理可变参数列表,这是必要的,以避免资源泄露。这段代码展示了如何安全地使用 vsnprintf 来格式化字符串,同时处理多个变量和确保缓冲区大小的限制。

注意事项:

  • 可变参数列表管理:使用 vsnprintf 时,需要使用 va_startva_end 来管理可变参数列表。此外,如果需要多次调用 vsnprintf 使用相同的参数列表,可以使用 va_copy 来复制参数列表。
  • 缓冲区溢出保护vsnprintf 会确保不会发生缓冲区溢出,但如果格式化后的字符串长度接近或等于 size,则应该检查返回值,确保没有发生截断。
  • 返回值vsnprintf 的返回值是格式化后的字符串的长度(不包括终止符)。如果这个长度大于或等于 size,说明字符串被截断了。
  • 类型安全format 字符串中的格式化指令必须与参数列表中的数据类型相匹配,否则可能导致未定义行为。
  • 跨平台兼容性vsnprintf 是一个较为现代的函数,大多数现代C库都支持它,但在一些老旧的或特定的环境中可能需要使用替代方案。

2.11 vsprintf函数

vsprintf 函数是C语言中的一个用于格式化字符串的函数,它是 sprintf 的变体,旨在处理可变参数列表。这使得 vsprintf 成为格式化字符串时更加灵活和强大的工具,尤其是在不确定参数数量的情况下。

函数原型:

int vsprintf(char *str, const char *format, va_list arg);
  • str: 目标字符串的指针,格式化后的字符串将被写入这里。
  • format: 控制输出格式的格式字符串。
  • arg: 一个 va_list 类型的参数,用于存储可变参数列表。

工作原理:

vsprintf 函数将 format 字符串中的占位符替换为 arg 参数列表中提供的值,并将结果写入到 str 指向的缓冲区中。与 sprintf 类似,vsprintf 将格式化后的字符串写入到预分配的缓冲区中,但与 sprintf 不同的是,vsprintf 允许使用可变参数列表,这使其更加通用。

示例代码:

#include <stdio.h>
#include <string.h>
#include <stdarg.h>

int main() {
    char buffer[100];
    int number = 123;
    double pi = 3.14159;
    const char *name = "John Doe";

    // 初始化可变参数列表
    va_list args;
    va_start(args, buffer);
    va_arg(args, int); // number
    va_arg(args, double); // pi
    va_arg(args, const char *); // name

    // 使用vsprintf格式化字符串
    vsprintf(buffer, "Hello, %s! The number is %d and pi is %.2f", args);

    // 清理可变参数列表
    va_end(args);

    // 输出格式化后的字符串
    printf("Formatted string: %s\n", buffer);

    return 0;
}

/* 输出结果:
*  Formatted string: Hello, John Doe! The number is 123 and pi is 3.14 */

在这个示例中,定义了一个可变参数列表 args,并使用 va_start 初始化它。然后,使用 va_arg 函数按顺序提取参数 numberpiname 到参数列表中。接下来,调用 vsprintf 函数,将这些参数按照格式字符串 "Hello, %s! The number is %d and pi is %.2f" 格式化,并将结果存储在 buffer 缓冲区中。最后,使用 va_end 来清理可变参数列表。

请注意,使用 va_arg 函数时,必须知道参数的类型,以正确地提取它们。这是因为 va_list 不保存参数的类型信息。在本例中,按照参数在调用 vsprintf 之前的顺序,依次提取了整型、双精度浮点型和字符指针类型的参数。

注意事项:

  • 可变参数列表管理:使用 vsprintf 时,需要使用 va_startva_end 来管理可变参数列表。此外,如果需要多次调用 vsprintf 使用相同的参数列表,可以使用 va_copy 来复制参数列表,但需要注意重新定位参数列表的位置,如示例中所示。
  • 缓冲区溢出保护:与 sprintf 一样,vsprintf 不会检查缓冲区溢出。如果格式化后的字符串长度超过 str 缓冲区的大小,将导致缓冲区溢出,这可能引发程序崩溃或安全漏洞。确保 str 缓冲区足够大,以避免这种情况。
  • 返回值vsprintf 的返回值是格式化后的字符串的长度(不包括终止符)。如果这个长度接近或等于缓冲区的大小,应该检查返回值,确保没有发生截断。
  • 类型安全format 字符串中的格式化指令必须与参数列表中的数据类型相匹配,否则可能导致未定义行为。
  • 跨平台兼容性vsprintf 在大多数C库中都是可用的,但在一些老旧的或特定的环境中可能需要使用替代方案。

2.12 vfprintf函数

vfprintf 函数是C语言中用于格式化输出到文件流的函数,类似于 fprintf,但接受一个可变参数列表。这使得 vfprintf 成为处理动态或不确定数量参数的格式化输出的理想选择。

函数原型:

int vfprintf(FILE *stream, const char *format, va_list arg);
  • stream: 文件流的指针,可以是 stdoutstderr 或任何打开的文件流。
  • format: 控制输出格式的格式字符串。
  • arg: 一个 va_list 类型的参数,用于存储可变参数列表。

工作原理:

vfprintf 函数根据 format 字符串中的指令,将 arg 参数列表中的值格式化,并输出到由 stream 指定的文件流中。与 fprintf 不同,vfprintf 允许你通过 va_list 来传递可变数量的参数,这在参数数量不确定时非常有用。

示例代码:

#include <stdio.h>
#include <stdarg.h>

void print_info(FILE *stream, const char *format, ...) {
    va_list args;
    va_start(args, format);
    vfprintf(stream, format, args);
    va_end(args);
}

int main() {
    int number = 42;
    double pi = 3.14159;
    const char *message = "Hello, World!";

    // 使用vfprintf通过函数print_info输出格式化信息
    print_info(stdout, "The answer is %d and pi is %.2f\n", number, pi);
    print_info(stderr, "%s\n", message);

    return 0;
}

/* 输出结果:
*  The answer is 42 and pi is 3.14
*  Hello, World! */

注意事项:

  • 可变参数列表管理:使用 vfprintf 时,需要使用 va_startva_end 来管理可变参数列表。此外,如果需要多次调用 vfprintf 使用相同的参数列表,可以使用 va_copy 来复制参数列表,但请注意清理旧的参数列表。
  • 格式化规则format 字符串中的格式化指令必须与参数列表中的数据类型相匹配,否则可能导致未定义行为或数据损坏。
  • 返回值vfprintf 的返回值是写入的字符数(不包括可能的NUL终止符)。如果返回值是负数,则表示输出过程中发生了错误。
  • 文件流vfprintf 可以将输出定向到任何文件流,不仅仅是标准输出和标准错误。确保文件流是打开状态的,并具有相应的写权限。
  • 跨平台兼容性vfprintf 在大多数C库中都是可用的,但在一些老旧的或特定的环境中可能需要使用替代方案。

2.13 memcpy函数

memcpy 是C标准库中的一个函数,用于将内存区域的内容复制到另一个内存区域。

函数原型:

void *memcpy(void *dest, const void *src, size_t n);
  • dest: 目标内存块的起始地址。
  • src: 源内存块的起始地址。
  • n: 要复制的字节数。

工作原理:

memcpy 函数从源内存区域 src 复制 n 个字节的数据到目标内存区域 dest。源和目标内存区域可以是任意类型,因为 memcpy 将其视为无类型的字节序列。该函数假设源和目标区域没有重叠。如果区域有重叠,memcpy 的行为是未定义的。

示例代码:

#include <stdio.h>
#include <string.h>

int main() {
    char source[50] = "Hello, World!";
    char destination[50];

    // 复制前12个字符
    memcpy(destination, source, 12);

    // 因为没有自动添加空字符,所以手动添加
    destination[12] = '\0';

    printf("Source: %s\n", source);
    printf("Destination: %s\n", destination);

    return 0;
}

/* 输出结果:
*  Source: Hello, World!
*  Destination: Hello, World */

注意事项:

  • 重叠区域:如果源和目标区域重叠,memcpy 的行为是未定义的。在这种情况下,应该使用 memmove 函数,它能够处理重叠的内存区域。
  • 字符串终止memcpy 不会自动添加字符串终止符。如果你复制的是字符串,你需要确保目标数组有足够的空间来存储字符串终止符,并且在复制后手动添加终止符,或者确保源字符串本身已经被正确终止。
  • 字节对齐memcpy 假设内存区域是适当对齐的。如果源或目标区域的对齐方式不正确,使用 memcpy 可能会导致未定义行为。
  • 性能考虑memcpy 在处理大量数据时通常比循环复制字节要快,因为它可能会利用处理器的硬件特性,如缓存行和向量指令。
  • 类型安全性:虽然 memcpy 可以用于任何类型的数据,但在复制复杂数据结构时,你应该确保理解数据布局,以避免意外覆盖其他数据。

2.14 memmove函数

memmove 函数是C语言中用于在内存中移动数据的一种方法,它与 memcpy 类似,但能处理源和目标区域重叠的情况。

函数原型:

void *memmove(void *dest, const void *src, size_t n);
  • dest: 目标内存区域的指针。
  • src: 源内存区域的指针。
  • n: 要复制的字节数。

工作原理:

memmove 函数将 n 个字节从 src 复制到 dest。与 memcpy 不同,memmove 可以处理源和目标区域重叠的情况,即当源和目标内存区域有部分或全部重叠时,它能够正确地移动数据而不会破坏原始数据。memmove 通过先将数据复制到一个临时缓冲区,再将数据写入目标位置,或者从后向前复制数据,来避免数据破坏。

示例代码:

#include <stdio.h>
#include <string.h>

int main() {
    char str[20] = "HelloWorld";
    char temp[20];

    // 使用memmove移动数据
    memmove(temp, str, 5); // 复制前5个字符
    memmove(str + 5, str + 10, 5); // 移动后5个字符到中间
    memmove(str, temp, 5); // 再次移动前5个字符到开头

    printf("Modified string: %s\n", str);

    return 0;
}

/* 输出结果:
*  Modified string: Hello */

注意事项:

  • 重叠区域memmove 特别适用于处理源和目标区域重叠的情况。当源和目标区域有重叠时,使用 memcpy 可能会导致数据损坏或丢失,而 memmove 能够避免这种问题。
  • 效率:尽管 memmove 能够处理重叠区域,但它可能比 memcpy 效率低,因为它可能需要额外的步骤(如使用临时缓冲区)来避免数据破坏。
  • 类型安全memmovememcpy 都将数据视为字节流,因此可以用于任何类型的数据。但是,如果数据包含复杂结构,直接使用 memmovememcpy 可能会导致类型安全问题,除非你完全理解数据布局。
  • 返回值memmove 返回目标区域的指针 dest,这与 memcpy 的行为一致,可以用于链式调用或进一步处理。

2.15 memset函数

memset 函数在C语言中用于将一块连续的内存区域填充为特定的值,通常用于初始化或清除内存。

函数原型:

void *memset(void *ptr, int value, size_t num);
  • ptr: 指向要填充的内存块的指针。
  • value: 要填入的值,这个值会被转换成 unsigned char 类型,通常用来填充8位的字节。
  • num: 要填充的字节数。

工作原理:

memset 函数将 ptr 所指向的内存块中的前 num 个字节设置为 valuevalue 参数会被转换为 unsigned char 类型,这意味着即使你传递一个较大的整数,也只有它的最低8位会被使用。该函数修改内存区域,并返回 ptr,即被填充的内存块的起始地址。

示例代码:

#include <stdio.h>
#include <string.h>

int main() {
    char str[20];

    // 使用memset初始化字符串
    memset(str, 'A', 10); // 将前10个字节设置为'A'
    str[10] = '\0'; // 添加终止符

    printf("Initialized string: %s\n", str);

    return 0;
}

/* 输出结果:
*  Initialized string: AAAAAAAAAA */

注意事项:

  • 类型安全:尽管 memset 可以用于任何类型的数据,但要注意 value 参数是按字节设置的,这意味着对于大于8位的数据类型,你可能需要更复杂的初始化逻辑。
  • 返回值memset 返回的仍然是 ptr,这使得它可以用于链式赋值或进一步的内存操作。
  • 初始化和清除memset 常用于初始化或清除内存区域。例如,将一个整型数组的所有元素设置为0,或者将一个字符串初始化为空。
  • 效率memset 通常比显式的循环填充更快,因为它可能利用了CPU的优化,如向量化操作。
  • 零填充:使用 memset 将内存区域设置为0(memset(ptr, 0, num);)是一种常见的做法,用于初始化变量或清除内存。

2.16 strdup函数

strdup 是一个常用的字符串操作函数,用于复制字符串到一个新的内存区域,并返回指向这个新复制的字符串的指针。

函数原型:

char *strdup(const char *str);
  • str: 指向要复制的源字符串的指针。

工作原理:

strdup 函数首先计算源字符串的长度(包括终止符),然后分配足够大小的内存来存储该字符串的副本。接着,它使用 strcpy 或类似函数将源字符串复制到新分配的内存中。最后,strdup 返回指向新复制字符串的指针。

示例代码:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main() {
    const char *original = "Hello, World!";
    char *copy;

    // 复制字符串
    copy = strdup(original);

    // 输出复制后的字符串
    printf("Original: %s\n", original);
    printf("Copy: %s\n", copy);

    // 释放新分配的内存
    free(copy);

    return 0;
}

/* 输出结果:
*  Original: Hello, World!
*  Copy: Hello, World! */

注意事项:

  • 内存管理strdup 分配的内存需要使用 free 函数手动释放。忘记释放这个内存会导致内存泄漏。
  • 失败处理:如果内存分配失败(例如,系统内存不足),strdup 将返回 NULL。在使用 strdup 的返回值之前,应该检查它是否为 NULL
  • 线程安全:在多线程环境中,strdup 的行为可能不是线程安全的,具体取决于底层的内存分配函数。如果在多线程程序中使用 strdup,可能需要采取额外的同步措施。
  • 非标准函数:虽然 strdup 被广泛使用,但它不是C标准库的一部分。在某些实现中,可能需要包含特定的头文件或链接到特定的库才能使用 strdup
  • 空字符串处理strdup 能够正确处理空字符串(即只包含终止符的字符串),返回一个指向同样只包含终止符的新字符串的指针。

2.17 strndup函数

strndup 是一个用于复制字符串的函数,与 strdup 类似,但允许你指定复制的最大字符数。这使得 strndup 成为一个更安全的选项,因为它可以帮助避免缓冲区溢出的问题。

函数原型:

char *strndup(const char *str, size_t n);
  • str: 指向要复制的源字符串的指针。
  • n: 要复制的字符数(不包括终止符)。

工作原理:

strndup 函数首先分配一个足够大的内存区域,该区域至少可以容纳 n 个字符加上一个终止符。然后,它使用 strncpy 或类似函数来复制最多 n 个字符(如果源字符串的长度小于 n,则复制整个字符串)到新分配的内存中。无论源字符串的长度如何,strndup 都会确保新复制的字符串以终止符结束。最后,strndup 返回指向新复制字符串的指针。

示例代码1:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main() {
    const char *original = "Programming is fun!";
    char *partial_copy;

    // 尝试复制前10个字符
    partial_copy = strndup(original, 10);
    if (partial_copy != NULL) {
        printf("Partial Copy: '%s'\n", partial_copy);
        free(partial_copy); // 释放内存
    } else {
        printf("Failed to allocate memory for partial_copy.\n");
    }

    return 0;
}

/* 输出结果:
*  Partial Copy: 'Programmi' */

复制了 "Programming is fun!" 的前10个字符,得到 "Programmi",注意即使源字符串在第10个字符之前就结束了,strndup 也会确保复制的字符串以终止符结束。

示例代码2:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main() {
    const char *original = "C Programming";
    char *partial_copy;

    // 尝试复制前20个字符(超过实际长度)
    partial_copy = strndup(original, 20);
    if (partial_copy != NULL) {
        printf("Partial Copy: '%s'\n", partial_copy);
        free(partial_copy); // 释放内存
    } else {
        printf("Failed to allocate memory for partial_copy.\n");
    }

    return 0;
}

/* 输出结果:
*  Partial Copy: 'C Programming' */

尽管尝试复制的字符数(20)超过了源字符串的实际长度,strndup 仍然复制了整个字符串 "C Programming",并确保复制的字符串以终止符结束。

注意事项:

  • 内存管理:与 strdup 类似,strndup 分配的内存也需要使用 free 函数手动释放,否则会导致内存泄漏。
  • 失败处理:如果内存分配失败,strndup 将返回 NULL。在使用 strndup 的返回值之前,应该检查它是否为 NULL
  • 终止符的处理:即使源字符串在 n 个字符之前就结束了,strndup 也会在新分配的内存中添加终止符,确保复制的字符串是有效的C字符串。
  • 非标准函数strndup 也不是C标准库的一部分,它的可用性取决于具体的编译器和库实现。在某些系统上,可能需要链接到特定的库才能使用 strndup
  • 安全性和效率strndup 通过限制复制的字符数来增强安全性,但它可能不如 strdup 那样高效,因为 strndup 需要为可能不需要的额外字符分配内存。

2.18 strtok函数

strtok 函数在C语言中用于将字符串分割成一系列的子字符串(标记),通常用于解析文本数据。

函数原型:

strtok 实际上有两种形式,它们分别如下:

char *strtok(char *str, const char *delim);
char *strtok_r(char *str, const char *delim, char **lasts);

第一种形式不推荐在多线程环境下使用,因为它使用了静态内部变量。第二种形式 strtok_r 是线程安全的版本,它使用了额外的参数 lasts 来跟踪上次调用的状态。

  • str: 指向要分割的字符串的指针。
  • delim: 指向一个字符串,其中包含一个或多个分隔符,用于确定如何分割字符串。
  • lasts: (仅针对 strtok_r)一个指向 char * 的指针,用于在多次调用间保持状态。

工作原理:

strtok 函数首次调用时,它会根据 delim 中定义的分隔符来分割 str 字符串,并返回指向第一个子字符串的指针。随后的调用中,strtok 会继续从上次停止的地方开始分割字符串,返回下一个子字符串的指针。当没有更多的子字符串可以返回时,strtok 返回 NULL

示例代码:

#include <stdio.h>
#include <string.h>

int main() {
    char str[] = "apple,banana,orange,grape";
    char *token;
    const char *delimiters = ",";

    token = strtok(str, delimiters);
    while (token != NULL) {
        printf("%s\n", token);
        token = strtok(NULL, delimiters);
    }

    return 0;
}

/* 输出结果:
*  apple
*  banana
*  orange
*  grape */

注意事项:

  • 多次调用strtok 需要多次调用以遍历整个字符串。首次调用时需要提供完整的字符串和分隔符,后续调用只需提供分隔符即可。
  • 修改原字符串strtok 会修改原字符串,插入空字符来分割子字符串,因此不要在原字符串上进行其他操作,直到 strtok 完成其任务。
  • 空字符串和连续分隔符:如果字符串以分隔符结尾或包含连续的分隔符,strtok 会返回空字符串或连续的空字符串。
  • 线程安全:由于 strtok 修改全局状态,因此在多线程程序中使用 strtok 可能会导致数据竞争。建议在多线程环境下使用 strtok_r
  • 返回值strtok 返回 NULL 表示没有更多子字符串可分割,或者在首次调用时字符串为空或仅由分隔符组成。
  • 分隔符字符串delim 参数可以是一个或多个字符。如果包含多个字符,那么每个字符都会被视为独立的分隔符。

3. 字符串比较、查找相关函数

3.1 strcmp函数

strcmp 函数在C语言中用于比较两个字符串,以确定它们是否相等,或者它们在字典顺序上的相对位置。

函数原型:

int strcmp(const char *str1, const char *str2);
  • str1: 指向第一个字符串的指针。
  • str2: 指向第二个字符串的指针。

工作原理:

strcmp 函数从第一个字符开始逐个比较两个字符串的相应字符,直到找到不匹配的字符或遇到字符串终止符 \0。比较时,它使用字符的ASCII码值。如果两个字符串完全相同,strcmp 返回0;如果 str1 应在字典顺序上排在 str2 之前,strcmp 返回一个小于0的值;如果 str1 应在字典顺序上排在 str2 之后,strcmp 返回一个大于0的值。

示例代码:

#include <stdio.h>
#include <string.h>

int main() {
    const char *str1 = "apple";
    const char *str2 = "banana";
    const char *str3 = "apple";

    int result1 = strcmp(str1, str2);
    int result2 = strcmp(str1, str3);

    if (result1 < 0) {
        printf("\"%s\" comes before \"%s\" in lexicographical order.\n", str1, str2);
    } else if (result1 > 0) {
        printf("\"%s\" comes after \"%s\" in lexicographical order.\n", str1, str2);
    } else {
        printf("\"%s\" and \"%s\" are equal.\n", str1, str2);
    }

    if (result2 == 0) {
        printf("\"%s\" and \"%s\" are equal.\n", str1, str3);
    }

    return 0;
}

/* 输出结果:
*  "apple" comes before "banana" in lexicographical order.
*  "apple" and "apple" are equal. */

注意事项:

  • 比较的是内容,而非地址strcmp 比较的是字符串的内容,而不是它们在内存中的地址。
  • 区分大小写strcmp 区分大小写,因此 "Apple""apple" 会被认为是不同的字符串。
  • 空字符串的处理strcmp 能够正确处理空字符串(即只包含终止符的字符串)。空字符串在字典顺序上排在所有非空字符串之前。
  • 性能考虑strcmp 的时间复杂度是O(n),其中n是两个字符串中最长的那个的长度,因为最坏情况下需要比较所有的字符。
  • 其他字符串比较函数:除了 strcmp,还有其他用于字符串比较的函数,如 strncmp(比较字符串的前n个字符),和 strcasecmp(在某些实现中,忽略大小写的比较)。

3.2 strncmp函数

strncmp 函数在C语言中用于比较两个字符串的前n个字符,这对于比较字符串的前缀或者在不确定字符串长度的情况下进行比较是非常有用的。

函数原型:

int strncmp(const char *str1, const char *str2, size_t n);
  • str1: 指向第一个字符串的指针。
  • str2: 指向第二个字符串的指针。
  • n: 要比较的字符数。

工作原理:

strncmp 函数从第一个字符开始比较两个字符串的前n个字符,使用字符的ASCII码值进行比较。如果在n个字符内两个字符串相等,strncmp 返回0;如果 str1 的前n个字符在字典顺序上排在 str2 的前n个字符之前,strncmp 返回一个小于0的值;反之,如果 str1 的前n个字符在字典顺序上排在 str2 的前n个字符之后,strncmp 返回一个大于0的值。如果在n个字符内有一个字符串已经结束(遇到了字符串终止符\0),则比较也停止。

示例代码:

#include <stdio.h>
#include <string.h>

int main() {
    const char *str1 = "apple";
    const char *str2 = "application";
    const char *str3 = "app";
    const char *str4 = "appetizer";

    int result1 = strncmp(str1, str2, 3);
    int result2 = strncmp(str3, str4, 3);
    int result3 = strncmp(str1, str2, 5);

    printf("Comparing \"%s\" and \"%s\" for first 3 characters: %d\n", str1, str2, result1);
    printf("Comparing \"%s\" and \"%s\" for first 3 characters: %d\n", str3, str4, result2);
    printf("Comparing \"%s\" and \"%s\" for first 5 characters: %d\n", str1, str2, result3);

    return 0;
}

/* 输出结果:
*  Comparing "apple" and "application" for first 3 characters: 0
*  Comparing "app" and "appetizer" for first 3 characters: 0
*  Comparing "apple" and "application" for first 5 characters: -1 */

注意事项:

  • n的含义n 参数指定要比较的字符数,包括字符串中的实际字符和可能的终止符。如果n等于或大于任一字符串的长度,strncmp 会比较到字符串的终止符为止。
  • 终止符的影响:如果在n个字符内有一个字符串已经结束,strncmp 会将终止符\0 视为一个普通字符来进行比较。
  • 区分大小写strncmp 区分大小写,因此 "Apple""apple" 会被认为是不同的字符串。
  • 性能考虑strncmp 的时间复杂度是O(min(n, m)),其中m是两个字符串中较短的那个的长度,因为最坏情况下需要比较所有指定的字符。
  • 边界条件:如果n为0,strncmp 会立即返回0,因为没有字符需要比较。

3.3 strstr函数

strstr 函数在C语言中用于搜索一个字符串在另一个字符串中的首次出现位置。

函数原型:

char *strstr(const char *haystack, const char *needle);
  • haystack: 指向要搜索的主字符串的指针。
  • needle: 指向要查找的子字符串的指针。

工作原理:

strstr 函数搜索 haystack 字符串中首次出现的 needle 子字符串,并返回指向该子字符串在 haystack 中开始位置的指针。如果找到了子字符串,strstr 返回指向该子字符串首字符的指针;如果没有找到,strstr 返回 NULL

strstrhaystack 的第一个字符开始搜索,直到找到 needle 或者达到 haystack 的字符串终止符 \0

示例代码:

#include <stdio.h>
#include <string.h>

int main() {
    const char *haystack = "This is a simple example.";
    const char *needle = "simple";

    char *result = strstr(haystack, needle);
    if (result != NULL) {
        printf("Found substring \"%s\" at position: %lu\n", needle, result - haystack);
    } else {
        printf("Substring \"%s\" not found.\n", needle);
    }

    return 0;
}

/* 输出结果:
*  Found substring "simple" at position: 10 */

注意事项:

  • 返回值类型strstr 返回的是 char * 类型,即指向 haystack 中子字符串开始位置的指针。如果子字符串不存在,返回 NULL
  • 空字符串处理strstr 能够正确处理空字符串(即只包含终止符的字符串)。如果 needle 是空字符串,strstr 总是返回 haystack 的指针,因为在任何字符串的任意位置之后都可以插入一个空字符串。
  • 区分大小写strstr 区分大小写,因此 "Simple""simple" 会被认为是不同的字符串。
  • 子字符串的长度:如果 needle 的长度为0,strstr 将返回 haystack 的指针,因为一个空字符串可以出现在任何字符串的任意位置。
  • 性能考虑strstr 的时间复杂度是O(m*n),其中m是 haystack 的长度,n是 needle 的长度。在最坏的情况下,strstr 可能需要比较 haystack 中的每个字符。
  • 其他搜索函数:除了 strstr,还有其他用于搜索字符串的函数,如 strchr(查找单个字符首次出现的位置)和 strrchr(查找单个字符最后一次出现的位置)。

3.4 strchr函数

strchr 函数在C语言中用于查找一个给定字符在字符串中的首次出现位置。

函数原型:

char *strchr(const char *str, int c);
  • str: 指向要搜索的字符串的指针。
  • c: 要查找的字符,类型为 int,这是因为ASCII字符集中的每个字符都可以用一个整数表示。

工作原理:

strchr 函数搜索 str 字符串中首次出现的字符 c,并返回指向该字符的指针。如果找到了该字符,strchr 返回指向该字符的指针;如果没有找到,或者 c 是字符串终止符 \0 并且它不在字符串中(即字符串不为空),strchr 返回 NULL

示例代码:

#include <stdio.h>
#include <string.h>

int main() {
    const char *str = "Hello, World!";
    char target = 'o';

    char *result = strchr(str, target);
    if (result != NULL) {
        printf("Character '%c' found at position: %lu\n", target, result - str);
    } else {
        printf("Character '%c' not found.\n", target);
    }

    return 0;
}

/* 输出结果:
*  Character 'o' found at position: 4 */

注意事项:

  • 返回值类型strchr 返回的是 char * 类型,即指向 str 中首次出现的 c 字符的指针。如果字符不存在于字符串中,返回 NULL
  • 字符编码c 参数是 int 类型,这是因为ASCII字符集中的每个字符都可以用一个整数表示,包括特殊字符和控制字符。
  • 字符串终止符:如果 c 等于字符串终止符 \0,并且字符串本身不为空,strchr 会返回 NULL,因为 \0 不应被视为字符串的一部分。
  • 区分大小写strchr 区分大小写,因此 'H''h' 会被认为是不同的字符。
  • 性能考虑strchr 的时间复杂度是O(n),其中n是字符串的长度。在最坏的情况下,strchr 可能需要检查字符串中的每个字符。
  • 其他搜索函数:除了 strchr,还有其他用于搜索字符串的函数,如 strrchr(查找字符最后一次出现的位置)和 strstr(查找子字符串的位置)。

3.5 strrchr函数

strrchr 函数在C语言中用于查找一个给定字符在字符串中最后一次出现的位置。

函数原型:

char *strrchr(const char *str, int c);
  • str: 指向要搜索的字符串的指针。
  • c: 要查找的字符,类型为 int,这是因为ASCII字符集中的每个字符都可以用一个整数表示。

工作原理:

strrchr 函数搜索 str 字符串中最后一次出现的字符 c,并返回指向该字符的指针。如果找到了该字符,strrchr 返回指向该字符的指针;如果没有找到,或者 c 是字符串终止符 \0 并且它不在字符串中(即字符串不为空),strrchr 返回 NULL

strrchr 从字符串的末尾开始向前搜索,直至找到字符 c 或者到达字符串的开始位置。

示例代码:

#include <stdio.h>
#include <string.h>

int main() {
    const char *str = "Hello, World!";
    char target = 'o';

    char *result = strrchr(str, target);
    if (result != NULL) {
        printf("Character '%c' found at position: %lu\n", target, result - str);
    } else {
        printf("Character '%c' not found.\n", target);
    }

    return 0;
}

/* 输出结果:
*  Character 'o' found at position: 8 */

注意事项:

  • 返回值类型strrchr 返回的是 char * 类型,即指向 str 中最后一次出现的 c 字符的指针。如果字符不存在于字符串中,返回 NULL
  • 字符编码c 参数是 int 类型,这是因为ASCII字符集中的每个字符都可以用一个整数表示,包括特殊字符和控制字符。
  • 字符串终止符:如果 c 等于字符串终止符 \0strrchr 会返回指向字符串末尾的指针,即使字符串不为空。这是因为每个C字符串都以 \0 结束。
  • 区分大小写strrchr 区分大小写,因此 'H''h' 会被认为是不同的字符。
  • 性能考虑strrchr 的时间复杂度是O(n),其中n是字符串的长度。在最坏的情况下,strrchr 可能需要检查字符串中的每个字符。
  • 其他搜索函数:除了 strrchr,还有其他用于搜索字符串的函数,如 strchr(查找字符首次出现的位置)和 strstr(查找子字符串的位置)。

3.6 strspn函数

strspn 函数在C语言中用于计算字符串开头连续的字符与给定的一组字符相匹配的最长长度。

函数原型:

size_t strspn(const char *s, const char *accept);
  • s: 指向要检查的字符串的指针。
  • accept: 指向一个包含一组字符的字符串,这些字符将被用来判断 s 开头的字符是否匹配。

工作原理:

strspn 函数计算 s 字符串开头连续的字符与 accept 字符串中字符相匹配的最长长度。一旦遇到一个不在 accept 字符串中的字符,strspn 就会停止计数并返回已经计数的字符数量。如果 s 的第一个字符就不在 accept 中,strspn 将返回0。

示例代码:

#include <stdio.h>
#include <string.h>

int main() {
    const char *str = "Hello, World!";
    const char *accept = "loW";

    size_t length = strspn(str, accept);

    printf("Length of initial segment matching '%s': %zu\n", accept, length);

    return 0;
}

/* 输出结果:
*  Length of initial segment matching 'loW': 0 */

注意事项:

  • 返回值类型strspn 返回的是 size_t 类型,表示匹配的字符数量。size_t 是一个无符号整数类型,用于表示对象的大小。
  • 区分大小写strspn 区分大小写,因此 'H''h' 会被认为是不同的字符。
  • 空字符串处理:如果 saccept 是空字符串(只包含字符串终止符),strspn 将返回0,因为没有字符可以匹配。
  • 性能考虑strspn 的时间复杂度是O(m),其中m是 s 字符串的长度,因为它需要遍历 s 直至找到第一个不匹配的字符。
  • 其他相关函数:除了 strspn,还有 strcspn 函数,它计算字符串开头连续的字符与给定的一组字符都不匹配的最长长度。

3.7 strcspn函数

strcspn 函数在C语言中用于计算从一个字符串的开头到遇到某个指定字符集合中的字符之前的长度。

函数原型:

size_t strcspn(const char *s, const char *reject);
  • s: 指向要检查的字符串的指针。
  • reject: 指向一个包含一组字符的字符串,这些字符将被用来判断 s 中哪些字符不应被计算在内。

工作原理:

strcspn 函数计算 s 字符串开头连续的字符与 reject 字符串中字符都不匹配的最长长度。一旦遇到一个在 reject 字符串中的字符,strcspn 就会停止计数并返回已经计数的字符数量。如果 s 的第一个字符就在 reject 中,strcspn 将返回0。

示例代码:

#include <stdio.h>
#include <string.h>

int main() {
    const char *str = "Hello, World!";
    const char *reject = " ,!";

    size_t length = strcspn(str, reject);

    printf("Length of initial segment without '%s': %zu\n", reject, length);

    return 0;
}

/* 输出结果:
*  Length of initial segment without ' ,!': 5 */

注意事项:

  • 返回值类型strcspn 返回的是 size_t 类型,表示没有遇到 reject 中字符的初始序列的长度。size_t 是一个无符号整数类型,用于表示对象的大小。
  • 区分大小写strcspn 区分大小写,因此 'H''h' 会被认为是不同的字符。
  • 空字符串处理:如果 sreject 是空字符串(只包含字符串终止符),strcspn 将返回 strlen(s),因为没有字符可以排除。
  • 性能考虑strcspn 的时间复杂度是O(m),其中m是 s 字符串的长度,因为它需要遍历 s 直至找到第一个在 reject 中的字符或到达字符串的结尾。
  • 其他相关函数:除了 strcspn,还有 strspn 函数,它计算字符串开头连续的字符与给定的一组字符相匹配的最长长度。

3.8 memcmp函数

memcmp 函数在C语言中用于比较两个内存区域的前n个字节,这通常用于比较字符串或其他固定长度的数据结构。

函数原型:

int memcmp(const void *s1, const void *s2, size_t n);
  • s1: 指向第一个内存区域的指针。
  • s2: 指向第二个内存区域的指针。
  • n: 要比较的字节数。

工作原理:

memcmp 函数比较 s1s2 所指向的前 n 个字节。如果两个区域的前 n 个字节完全相同,memcmp 返回0;如果 s1 的前 n 个字节在字典顺序上排在 s2 的前 n 个字节之前,memcmp 返回一个小于0的值;如果 s1 的前 n 个字节在字典顺序上排在 s2 的前 n 个字节之后,memcmp 返回一个大于0的值。比较是基于字节的数值,即每个字节的ASCII码值。

示例代码:

#include <stdio.h>
#include <string.h>

int main() {
    char str1[] = "Hello";
    char str2[] = "Hella";
    char str3[] = "Hello";
    char str4[] = "hello";

    int result1 = memcmp(str1, str2, 5);
    int result2 = memcmp(str1, str3, 5);
    int result3 = memcmp(str1, str4, 5);

    printf("Comparing \"%s\" and \"%s\": %d\n", str1, str2, result1);
    printf("Comparing \"%s\" and \"%s\": %d\n", str1, str3, result2);
    printf("Comparing \"%s\" and \"%s\": %d\n", str1, str4, result3);

    return 0;
}

/* 输出结果:
*  Comparing "Hello" and "Hella": 1
*  Comparing "Hello" and "Hello": 0
*  Comparing "Hello" and "hello": -1 */

注意事项:

  • 区分大小写memcmp 区分大小写,因此 "Hello""hello" 会被认为是不同的序列。
  • 比较的是字节memcmp 比较的是字节,而不是字符。这意味着如果两个字符串包含多字节字符(如UTF-8编码的非ASCII字符),memcmp 的结果可能与直观的字符比较结果不同。
  • 空字符串的处理memcmp 能够正确处理空字符串(即只包含终止符的字符串)。如果两个字符串都是空字符串,memcmp 返回0。
  • 性能考虑memcmp 的时间复杂度是O(n),其中n是最长的比较长度。在最坏的情况下,需要比较所有的字节。
  • 其他比较函数:除了 memcmp,还有 strcmp(用于比较字符串,直到遇到字符串终止符),strncmp(用于比较字符串的前n个字符)。
  • 13
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

studyingdda

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值