内容预览
- 本文中所有设计的代码均通过测试,并且在功能性方面均实现应有的功能。
- 如果你也对此感兴趣、也想测试源码的话,可以私聊我,非常欢迎一起探讨学习。
- 由于时间、水平、精力有限,文中难免会出现不准确、甚至错误的地方,也很欢迎大佬看见的话批评指正。
一般系统中提供一个标准的C库 string.h ,用于操作各种操作字符串( strxxx )、内存( memxxx )的库函数。作为 C标准库 的一部分,它们被强制要求可以在任何支持 C语言 的平台上运行。 不管是在面试中还是平时的工作中,对 string.h 文件中的大部分的函数的都有涉及,并且会经常遇到手撕某个或者某几个库函数的题目,其重要程度可想而知。
续接上文:
C语言 – string.h中函数功能详解与手动实现 - 01(常用函数memset、memcmp、strcmp、strncmp …)
C语言 – string.h中函数功能详解与手动实现 - 02(常用函数memcpy、memmove、strcpy、strdup、strcat、strtok…)
3.9、查询类型 — 函数功能详细说明 :在内存空间/字符串查找特定字符(串)
void *memchr(const void *s, int c, size_t n);
功能:扫描指针 s 指向的内存区域的前 n 个字节,找到第一次出现字符 c 的位置
返回:1、如果成功找到字符c,返回指向字符 c 的指针
2、如果在指定区域中没有找到字符 c,则返回 NULL
说明:1、当第一次遇到字符 c 时即停止查找
2、c 和 s 指向的内存区域的字节都被解释为无符号字符
3、需要保证 n 的有效值与 s 指向内存区域大小的匹配性
如果 n 大于 s 指向内存区域大小,则对于未找到 c 的情况,返回值的将不可控
void *memrchr(const void *s, int c, size_t n);
功能:扫描指针 s 指向的内存区域末尾的后 n 个字节,找到最后一次出现字符 c 的位置
返回:1、如果成功找到字符c,返回指向字符 c 的指针
2、如果在指定区域中没有找到字符 c,则返回 NULL
说明:1、当最后一次遇到字符 c 时即停止查找
2、c 和 s 指向的内存区域的字节都被解释为无符号字符
3、为GNU的扩展,不是C语言的一部分,最早出现在glibc的2.2版中
4、需要 #define _GNU_SOURCE
void *rawmemchr(const void *s, int c);
功能:扫描指针 s 指向的内存区域出现字符 c 的位置
返回:1、如果成功找到字符c,返回指向字符 c 的指针
2、如果在指定区域中没有找到字符,则结果是不可预测的
说明:1、与函数 memchr 类似,可理解为函数 memchr()的优化版本
2、假设确定在 s 所指区域中必须包含至少一个字符 c,因此对c执行优化搜索
3、为GNU的扩展,不是C语言的一部分,最早出现在glibc的2.1版中
4、需要 #define _GNU_SOURCE
5、快速查找字符串的终止空字节的方法 char *p = rawmemchr(s, '\0');
char *strchr(const char *s, int c);
功能:查找字符串 s 中首次出现字符 c 的位置
返回:1、如果找到 c,则返回首次出现 c 的位置的指针
2、如果 s 中不存在 c,则返回 NULL。
说明:1、终止的空字节被认为是字符串的一部分,因此如果 c 被指定为'\0',则将返回指向终止符的指针
2、函数不适用于宽字符或多字节字符
3、c 和 s 指向的字符串的字节都被解释为无符号字符(unsigned char)
4、如果 s 指向的字符串中不包含'\0',则将发生异常
char *strrchr(const char *s, int c);
功能:查找字符串 s 中最后一次出现字符 c 的位置
返回:1、如果找到字符 c, 则返回最后一次出现 c 的位置的指针
2、如果 s 中不存在 c, 则返回NULL
说明:与 strchr 一致
char *strstr(const char *haystack, const char *needle);
功能:从字符串 haystack 中寻找 needle 所指子字符串第一次出现的位置(不比较结束符 '\0')。
返回:1、如果匹配,则返回指向第一次出现 needle 所指子字符串位置的指针,
2、如果没找,则返回 NULL。
说明:1、如果子字符串 needle 为空(空字符串),则函数返回 haystack
2、如果子字符串 needle 长度为一个字节,使用与 strchr 类似
char *strcasestr(const char *haystack, const char *needle);
功能:从字符串 haystack 中寻找 needle 所指子字符串第一次出现的位置(不比较结束符 '\0')。
返回:1、如果匹配,则返回指向第一次出现 needle 所指子字符串位置的指针,
2、如果没找,则返回 NULL。
说明:1、如果子字符串 needle 为空(空字符串),则函数返回 haystack
2、如果子字符串 needle 长度为一个字节,使用与 strchr 类似
3、与strstr函数功能一致,唯一区别为此函数在匹配过程中不区分字符的大小写
4、是一个非标准扩展,需要 #define _GNU_SOURCE
char *strpbrk(const char *s, const char *accept);
功能:在字符串 s 中寻找字符串 accept 中任何一个字符相匹配的第一个字符的位置(不包含 '\0')
返回:1、如果匹配成功(至少一个字符),则返回指向 s 中第一个相匹配的字符的指针
2、如果没有匹配字符,则返回 NULL。
说明:1、如果 accept 为空字符串,则返回 NULL
2、如果在 s 字符串中存在完全匹配的 accept,则返回指向完全配的字符串的位置指针,
而不是匹配的第一个字符(或其他字符)
size_t strspn(const char *s, const char *accept);
功能:计算在字符串 s 中,有多少个连续的字符都属于字符串 accept
返回:从字符串 s 开头计算与字符串 accept 完全一致的字符的个数
说明:1、如果字符串 s 的第一个字符不属于 accept,那么返回 0
2、如果字符串 s 所指向的字符串都属于 accept,那么返回字符串 s 的长度
3、如果字符串 s 开头连续有 n 个字符都属于字符串 accept,则返回 n
4、如果 accept 为空字符串,则返回 0
size_t strcspn(const char *s, const char *reject);
功能:计算 reject 中某个字符在的 s 中第一个出现的位置
返回:reject 中某个字符在的 s 中第一个出现的下标值
说明:1、如果 s 和 reject 的交集为空,也就是 reject 中任何一个字符在 s 中不存在,
则返回 s 的字符串长度
2、如果 s 与 reject 的交集不为空,也就是 reject 与 s 中存在共同的字符,
那么返回在 s 中从前到后首次出现 reject 的某个字符下标
3、如果 accept 为空字符串('\0'),则返回 0
3.10、查询类型 — 函数功能测试与手动实现
3.10.1、memchr
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void *my_memchr(const void *s, int c, size_t n)
{
const unsigned char *s_in = (unsigned char *)s;
unsigned char c_in = (unsigned char)c;
for (; n > 0; --n, ++s_in)
{
if (*s_in == c_in)
return (void *)s_in;
}
return NULL;
}
int main(int argc, const char **argv)
{
const char *str = "I'm the test string";
printf("Before memchr(t), str = %s, length = %ld\n", str, strlen(str));
// memchr 测试
char *ret = (char *)memchr(str, 't', strlen(str));
printf("After memchr(t), str = %s, length = %ld\n", str, strlen(str));
printf(" ret = %s\n ", ret);
putchar(10);
ret = (char *)memchr(str, ';', strlen(str));
printf("After memchr(;), str = %s, length = %ld\n", str, strlen(str));
printf(" ret = %s\n ", ret);
putchar(10);
// memchr n 远远大于 有效区域 测试,同时字符串中存在要查找的字符
ret = (char *)memchr(str, 't', strlen(str) + 100000);
printf("After memchr(t,100000), str = %s, length = %ld\n", str, strlen(str));
printf(" ret = %s\n ", ret);
putchar(10);
// memchr n 远远大于 有效区域测试,同时有效字符串中不存在要查找的字符
// 下面的测试结果不定,具体按运行结果为准
ret = (char *)memchr(str, ';', strlen(str) + 1000000);
printf("After memchr(;,100000), str = %s, length = %ld\n", str, strlen(str));
printf(" ret = %s\n ", ret);
putchar(10);
// my_memchr 测试
ret = (char *)my_memchr(str, 't', strlen(str));
printf("After my_memchr(t), str = %s, length = %ld\n", str, strlen(str));
printf(" ret = %s\n ", ret);
putchar(10);
ret = (char *)my_memchr(str, ';', strlen(str));
printf("After my_memchr(;), str = %s, length = %ld\n", str, strlen(str));
printf(" ret = %s\n ", ret);
printf("\n\e[1;32msystem exited with return code %d\e[0m\n\n", 0);
return 0;
}
测试效果如下图所示。
3.10.2、strchr、strrchr
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *my_strchr(const char *s, int c)
{
const unsigned char *s_in = (const unsigned char *)s;
unsigned char c_in = (unsigned char)c;
for (; *s_in != '\0'; ++s_in)
{
if (*s_in == c_in) return (char *)s_in;
}
return NULL;
}
char *my_strrchr(const char *s, int c)
{
char *found = NULL, *p = NULL;
if (c == '\0') return strchr(s, '\0');
while ((p = strchr(s, c)) != NULL)
{
found = p;
s = p + 1;
}
return (char *)found;
}
int main(int argc, const char **argv)
{
char src[20];
const char *str = "I'm the test string";
/* strchr 测试 */
// strchr 的正常测试 -- 找到 c
printf("Before strchr(h), str = %s, length = %ld\n", str, strlen(str));
char *ret = strchr(str, 'h');
printf("After strchr(h), str = %s, length = %ld\n", str, strlen(str));
printf(" ret = %s\n", ret);
putchar(10);
// strchr 的正常测试 -- 未找到 c
ret = strchr(str, ';');
printf("After strchr(;), str = %s, length = %ld\n", str, strlen(str));
printf(" ret = %s\n", ret);
putchar(10);
memset(src, 'A', 20);
src[10] = 'B';
printf("Before strchr(B), str = %s, length = %ld\n", src, strlen(src));
// strchr 不正常测试 -- 找到 c
ret = strchr(src, 'B');
printf("After strchr(B), str = %s, length = %ld\n", str, strlen(str));
printf(" ret = %s\n", ret);
putchar(10);
// strchr 不正常测试 -- 未找到 c
ret = strchr(src, 'C');
printf("After strchr(C), str = %s, length = %ld\n", str, strlen(str));
printf(" ret = %s\n", ret);
putchar(10);
/* my_strchr 测试 */
// my_strchr 的正常测试 -- 找到 c
ret = my_strchr(str, 'h');
printf("After my_strchr(h), str = %s, length = %ld\n", str, strlen(str));
printf(" ret = %s\n", ret);
putchar(10);
// strchr 的正常测试 -- 未找到 c
ret = my_strchr(str, ';');
printf("After my_strchr(;), str = %s, length = %ld\n", str, strlen(str));
printf(" ret = %s\n", ret);
putchar(10);
/* strrchr 测试 */
printf("Before strrchr(e), str = %s, length = %ld\n", str, strlen(str));
ret = strrchr(str, 'e');
printf("After strrchr(e), str = %s, length = %ld\n", str, strlen(str));
printf(" ret = %s\n", ret);
putchar(10);
printf("Before strrchr(a), str = %s, length = %ld\n", str, strlen(str));
ret = strrchr(str, 'a');
printf("After strrchr(a), str = %s, length = %ld\n", str, strlen(str));
printf(" ret = %s\n", ret);
putchar(10);
/* my_strrchr 测试 */
printf("Before my_strrchr(e), str = %s, length = %ld\n", str, strlen(str));
ret = my_strrchr(str, 'e');
printf("After my_strrchr(e), str = %s, length = %ld\n", str, strlen(str));
printf(" ret = %s\n", ret);
putchar(10);
printf("Before my_strrchr(a), str = %s, length = %ld\n", str, strlen(str));
ret = my_strrchr(str, 'a');
printf("After my_strrchr(a), str = %s, length = %ld\n", str, strlen(str));
printf(" ret = %s\n", ret);
printf("\n\e[1;32msystem exited with return code %d\e[0m\n\n", 0);
return 0;
}
测试效果如下图所示。
3.10.3、strspn、strcspn
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, const char **argv)
{
char *s = "abcdefghABCDEFGH";
int pos = 0;
/* strspn 测试 */
printf("Before strspn, s = %s, strlen(s) = %ld, accept = abcdefghABCDEFGH\n", s, strlen(s));
pos = strspn(s, "abcdefghABCDEFGH");
printf("After strspn, ret = %d\n", pos);
putchar(10);
printf("Before strspn, s = %s, strlen(s) = %ld, accept = abcdefghABCDEFG\n", s, strlen(s));
pos = strspn(s, "abcdefghABCDEFG");
printf("After strspn, ret = %d\n", pos);
putchar(10);
printf("Before strspn, s = %s, strlen(s) = %ld, accept = abcdCDEFG\n", s, strlen(s));
pos = strspn(s, "abcdefghABCDEFG");
printf("After strspn, ret = %d\n", pos);
putchar(10);
printf("Before strspn, s = %s, strlen(s) = %ld, accept = bcdefghABCDEFGH\n", s, strlen(s));
pos = strspn(s, "efghABC");
printf("After strspn, ret = %d\n", pos);
putchar(10);
printf("Before strspn, s = %s, strlen(s) = %ld, accept = sdef\n", s, strlen(s));
pos = strspn(s, "sdef");
printf("After strspn, ret = %d\n", pos);
putchar(10);
printf("Before strspn, s = %s, strlen(s) = %ld, accept = ''\n", s, strlen(s));
pos = strspn(s, "");
printf("After strspn, ret = %d\n", pos);
putchar(10);
/* strcspn 测试 */
// 下标值
printf("Before strcspn, s = %s, strlen(s) = %ld, accept = abcdefghABCDEFGH\n", s, strlen(s));
pos = strcspn(s, "abcdefghABCDEFGH");
printf("After strcspn, ret = %d\n", pos);
printf("Before strcspn, s = %s, strlen(s) = %ld, accept = abcd\n", s, strlen(s));
pos = strcspn(s, "abcd");
printf("After strcspn, ret = %d\n", pos);
printf("Before strcspn, s = %s, strlen(s) = %ld, accept = bcd\n", s, strlen(s));
pos = strcspn(s, "bcd");
printf("After strcspn, ret = %d\n", pos);
printf("Before strcspn, s = %s, strlen(s) = %ld, accept = ABCD\n", s, strlen(s));
pos = strcspn(s, "ABCD");
printf("After strcspn, ret = %d\n", pos);
// abcdefghABCDEFGH
printf("Before strcspn, s = %s, strlen(s) = %ld, accept = abcdefgh\n", s, strlen(s));
pos = strcspn(s, "abcdefgh");
printf("After strcspn, ret = %d\n", pos);
printf("Before strcspn, s = %s, strlen(s) = %ld, accept = bcdefgha\n", s, strlen(s));
pos = strcspn(s, "bcdefgha");
printf("After strcspn, ret = %d\n", pos);
printf("Before strcspn, s = %s, strlen(s) = %ld, accept = cdefghb\n", s, strlen(s));
pos = strcspn(s, "cdefghb");
printf("After strcspn, ret = %d\n", pos);
printf("Before strcspn, s = %s, strlen(s) = %ld, accept = cdefghab\n", s, strlen(s));
pos = strcspn(s, "cdefghab");
printf("After strcspn, ret = %d\n", pos);
printf("Before strcspn, s = %s, strlen(s) = %ld, accept = fghde\n", s, strlen(s));
pos = strcspn(s, "fghde");
printf("After strcspn, ret = %d\n", pos);
printf("Before strcspn, s = %s, strlen(s) = %ld, accept = fgh\n", s, strlen(s));
pos = strcspn(s, "fgh");
printf("After strcspn, ret = %d\n", pos);
printf("Before strcspn, s = %s, strlen(s) = %ld, accept = H\n", s, strlen(s));
pos = strcspn(s, "H");
printf("After strcspn, ret = %d\n", pos);
printf("Before strcspn, s = %s, strlen(s) = %ld, accept = ''\n", s, strlen(s));
pos = strcspn(s, "");
printf("After strcspn, ret = %d\n", pos);
printf("Before strcspn, s = %s, strlen(s) = %ld, accept = lmjk\n", s, strlen(s));
pos = strcspn(s, "lmjk");
printf("After strcspn, ret = %d\n", pos);
printf("\n\e[1;32msystem exited with return code %d\e[0m\n\n", 0);
return 0;
}
测试效果如下图所示。
3.11、属性类型 — 函数功能详细说明 :计算字符串包含的字符个数/长度
size_t strlen(const char *s);
功能:计算字符串 s 的长度,以'\0'判断结束,但是不包含'\0'
返回:返回字符串s的长度,不包括结束符’\0‘。
说明:计算的是字符串的实际长度,遇到第一个'\0'结束。
如果只定义没有给它赋初值或在有效空间内没有'\0'这个结果是不定的,会从首地址一直计算直到遇到'\0'停止
size_t strnlen(const char *s, size_t maxlen);
功能:计算 s 指向的字符串中的字符个数,以'\0'判断结束但是不包含'\0'
返回:1、如果 s 指向字符串的长度小于 maxlen,则函数返回字符串长度
2、如果 s 指向字符串的长度大于 maxlen 或者 第一个 maxlen 字符中没有遇到'\0',则函数返回 maxlen
说明:1、返回 s 指向的字符串中的字符数(字符串的长度),不包括终止的空字节('\0'),但最多为 maxlen
2、只查看由 s 指向的字符串中的第一个 maxlen 字符,不超过 s + maxlen 位置
3、不是C标准,符合 POSIX.1-2008,以前版本需要 #define _GNU_SOURCE
3.12、属性类型 — 函数功能测试与手动实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if WIN32
// 进行递归算法
size_t strnlen(const char *str, size_t maxlen)
{
return ((maxlen-- > 0) && ('\0' != *str)) ? (1 + strnlen(++str, maxlen)) : 0;
}
#endif
// 常规算法
size_t my_strlen1(const char *str)
{
const char *ptr;
for (ptr = str; *ptr != '\0'; ++ptr)
;
return ptr - str;
}
// 常规算法
size_t my_strlen2(const char *str)
{
size_t len = 0;
while (*str++ != '\0')
len++;
return len;
}
size_t my_strlen3(const char *str)
{
return ('\0' != *str) ? (1 + strlen(++str)) : 0;
}
size_t my_strnlen(const char *str, size_t maxlen)
{
return ((maxlen-- > 0) && ('\0' != *str)) ? (1 + my_strnlen(++str, maxlen)) : 0;
}
size_t my_strnlen1(const char *str, size_t maxlen)
{
size_t len = 0;
while (*str++ != '\0' && maxlen--)
len++;
return len;
}
int main(int argc, const char **argv)
{
char *str = "hello world";
printf("strlen : %ld, %ld, %ld, %ld\n",
strlen(str), my_strlen1(str), my_strlen1(str), my_strlen2(str));
printf("strnlen : %ld, %ld, %ld\n",
strnlen(str, 10), my_strnlen1(str, 10), my_strnlen(str, 10));
printf("strnlen : %ld, %ld, %ld\n",
strnlen(str, 20), my_strnlen1(str, 20), my_strnlen(str, 20));
printf("\n\e[1;32msystem exited with return code %d\e[0m\n\n", 0);
return 0;
}
测试效果如下图所示。
3.13、转换类型 — 函数功能详细说明 :将字符串包含字符进行大小写转换/翻转
char *strupr(char *s);
功能:将 s 指向的字符串中小写形式转换为大写形式
返回:指向 s 的指针(此时指向 s 的字符串已经被改变)
说明:1、只转换 s 指向字符串中出现的小写字母,不改变其它字符
2、不会创建新字符串返回,而是改变原有字符串,所以不能转换字符串常量
3、不是标准的C库函数,在linux gcc环境下需要自行定义
char *strlwr(char *s);
功能:将 s 指向的字符串中大写形式转换为小写形式
返回:指向 s 的指针(此时指向 s 的字符串已经被改变)
说明:1、只转换 s 指向字符串中出现的小写字母,不改变其它字符
2、不会创建新字符串返回,而是改变原有字符串,所以不能转换字符串常量
3、不是标准的C库函数,在linux gcc环境下需要自行定义
char *strrev(char *str);
功能:翻转 s 指向的字符串,所有字符的顺序颠倒过来(不包括'\0')。
返回:指向翻转后的字符串指针 str
说明:1、修改原字符串,所以不能翻转字符串常量
2、不是C语言标准库函数,linux gcc环境下需要自行定义
3.14、转换类型 — 函数功能测试与手动实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 在windows环境下已经可以支持下面的函数功能
#if !WIN32
// 如果要使用C库函数实现
#if __CTYPE
#include <ctype.h>
// 使用C库函数实现
char *strlwr(char *str)
{
char *tmp = str;
while (*tmp)
{
*tmp = tolower(*tmp);
tmp++;
}
return str;
}
char *strupr(char *str)
{
char *tmp = str;
while (*tmp)
{
*tmp = toupper(*tmp);
tmp++;
}
return str;
}
#else
// 不用C库函数,自行实现
#define TOUPPER(s) ((s) >= 'a' && (s) <= 'z') ? (s - 'a' + 'A') : (s)
#define TOLOWER(s) ((s) >= 'A' && (s) <= 'Z') ? (s - 'A' + 'a') : (s)
char *strupr(char *str)
{
char *tmp = str;
while (*tmp)
{
*tmp = TOUPPER(*tmp);
tmp++;
}
return str;
}
char *strlwr(char *str)
{
char *tmp = str;
while (*tmp)
{
*tmp = TOLOWER(*tmp);
tmp++;
}
return str;
}
char *strrev(char *str)
{
char *p1 = str;
char *p2 = str + strlen(str) - 1;
int i = strlen(str) / 2;
while (i > 0)
{
*p1 ^= *p2;
*p2 ^= *p1;
*p1 ^= *p2;
p1++;
p2--;
i--;
}
return str;
}
#endif // end of __CTYPE
#endif // end of WIN32
int main(int argc, const char **argv)
{
char str[] = "Hello, I am SongShuai";
char *ret = NULL;
printf("Before strupr, str = %s\n", str);
ret = strupr(str);
printf("After strlwr, str = %s\n", str);
printf(" ret = %s\n", ret);
putchar(10);
printf("Before strlwr, str = %s\n", str);
ret = strlwr(str);
printf("After strlwr, str = %s\n", str);
printf(" ret = %s\n", ret);
putchar(10);
char str111[12]; // 随机内容进行测试,具体按照测试为准
printf("Before strlwr, str = %s\n", str111);
ret = strlwr(str111);
printf("After strlwr, str = %s\n", str111);
printf(" ret = %s\n", ret);
putchar(10);
printf("Before strrev, str = %s\n", str);
ret = strrev(str);
printf("After strlwr, str = %s\n", str);
printf(" ret = %s\n", ret);
printf("\n\e[1;32msystem exited with return code %d\e[0m\n\n", 0);
return 0;
}
测试效果如下图所示。
三、简单的总结
综上,基本上在 string.h 中 大大 部分函数功能都已经有了简单的说明。虽然有一些平台兼容性不好的功能函数(比如:strupr、strnlen …),但是基本上明白基本功能和特性,想要在其他平台实现兼容也是比较简单是事情。
上面的所有的功能函数都基于 glibc-2.27 版本,想要查看源码或者其他版本的也可以了自行下载和查看:
1、官网(虽说习惯上去官网,但下载速度真的慢): http://ftp.gnu.org/gnu/glibc/
2、清华大学开源软件镜像站点: https://ftpmirror.gnu.org/libc/
3、中国科技大学开源软件镜像站点: https://mirrors.ustc.edu.cn/gnu/libc/
好啦,废话不多说,总结写作不易,如果你喜欢这篇文章或者对你有用,请动动你发财的小手手帮忙点个赞,当然 关注一波 那就更好了,好啦,就到这儿了,么么哒(*  ̄3)(ε ̄ *)。
上一篇:C语言 – string.h中函数功能详解与手动实现 - 02(常用函数memcpy、memmove、strcpy、strdup、strcat、strtok…)
下一篇:本文