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); | 比较字符串 s1 和 s2 。 |
int strncmp(const char *s1, const char *s2, size_t n); | 比较字符串 s1 和 s2 的前 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); | 比较 s1 和 s2 的前 n 个字节。 |
1. 字符串长度相关函数
strlen
和strnlen
是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
是有效的,指向的是分配给字符串的内存区域。如果s
是NULL
或者指向未分配的内存,将导致运行时错误或程序崩溃。- 缓冲区大小:在使用
strlen
计算字符串长度后,如果计划使用这个长度,比如在malloc
或calloc
中申请内存,确保考虑到终止符的额外空间。- 性能考量:
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
。
工作原理:
strnlen
与 strlen
类似,但它在达到 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
。- 安全替代方案:如果目标缓冲区的大小有限或不确定源字符串的长度,考虑使用
strncpy
或strlcpy
等更安全的函数,它们允许你指定复制的最大字符数,从而避免缓冲区溢出。- 字符串复制后的处理:在复制字符串后,如果需要进一步处理目标字符串,例如追加更多文本,确保再次检查缓冲区的大小,以避免后续的缓冲区溢出。
2.2 strncpy函数
strncpy
函数是C语言中用于字符串操作的一个重要函数,它允许用户指定复制的字符数,这在处理固定长度的字符串或避免缓冲区溢出时非常有用。
函数原型:
char *strncpy(char *dest, const char *src, size_t n);
dest
: 目标字符串的指针,即复制后字符串存放的地方。src
: 源字符串的指针,即待复制的字符串。n
: 要复制的字符数,包括源字符串的任何字符,但不自动包括终止符\0
。
工作原理:
strncpy
函数会从 src
中复制最多 n
个字符到 dest
。如果 src
的长度小于 n
,strncpy
将会复制所有字符,包括终止符 \0
,并在剩余位置填充 \0
直至 n
。然而,如果 src
的长度大于或等于 n
,strncpy
仅复制 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
,仍然可能导致溢出。- 源字符串长度的考虑:如果源字符串的长度小于
n
,strncpy
会复制所有字符,并在目标字符串中剩余的位置填充\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
的末尾。- 安全替代品:对于已知或可能未知长度的字符串,使用
strncat
或strlcat
可以提供更安全的字符串连接,因为这些函数允许你指定目标缓冲区的大小,从而避免缓冲区溢出。
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
也会在目标字符串的末尾添加一个终止符,以确保结果字符串是有效的。- 安全性和效率:
strncat
比strcat
更安全,因为它限制了追加的字符数量,从而减少了缓冲区溢出的风险。然而,过度限制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
的区别:snprintf
与sprintf
类似,但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_start
和va_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
函数按顺序提取参数 number
、pi
和 name
到参数列表中。接下来,调用 vsprintf
函数,将这些参数按照格式字符串 "Hello, %s! The number is %d and pi is %.2f"
格式化,并将结果存储在 buffer
缓冲区中。最后,使用 va_end
来清理可变参数列表。
请注意,使用 va_arg
函数时,必须知道参数的类型,以正确地提取它们。这是因为 va_list
不保存参数的类型信息。在本例中,按照参数在调用 vsprintf
之前的顺序,依次提取了整型、双精度浮点型和字符指针类型的参数。
注意事项:
- 可变参数列表管理:使用
vsprintf
时,需要使用va_start
和va_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
: 文件流的指针,可以是stdout
、stderr
或任何打开的文件流。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_start
和va_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
效率低,因为它可能需要额外的步骤(如使用临时缓冲区)来避免数据破坏。- 类型安全:
memmove
和memcpy
都将数据视为字节流,因此可以用于任何类型的数据。但是,如果数据包含复杂结构,直接使用memmove
或memcpy
可能会导致类型安全问题,除非你完全理解数据布局。- 返回值:
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
个字节设置为 value
。value
参数会被转换为 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
。
strstr
从 haystack
的第一个字符开始搜索,直到找到 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
等于字符串终止符\0
,strrchr
会返回指向字符串末尾的指针,即使字符串不为空。这是因为每个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'
会被认为是不同的字符。- 空字符串处理:如果
s
或accept
是空字符串(只包含字符串终止符),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'
会被认为是不同的字符。- 空字符串处理:如果
s
或reject
是空字符串(只包含字符串终止符),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
函数比较 s1
和 s2
所指向的前 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个字符)。
文章评论