C语言进阶——字符串&&内存函数(上)

简介: 这是牛客网上的一道简单题:判断输入字符是否为字母,一般的解决方法是通过ASCII码判断,不过这样做的话判断表达式较长,此时我们可以利用C语言中的库函数isalpha(判断是否为字母) 来完成这个题目,不仅代码量少,而且通俗易懂。要实现这种效果,就需要学习C语言中的各种库函数,而本文会列出大多数字符串函数和内存函数的使用及其实现,如果你想学习C语言库函数或对字符串、内存有好奇之心,不妨仔细来看看吧!


目录


🌇前言


🌇正文


🌆字符串函数


🌉长度不可控的字符串函数


🌃strlen 长度统计


🌃strcpy 拷贝


🌃strcmp 比较


🌃strcat 追加


🌉长度可控的字符串操作函数


🌃strncpy 可控拷贝


🌃strncmp 可控比较


🌃strncat 可控追加


🌉特殊字符串函数


🌃strstr 寻找


🌃strtok 分割


🌃strerror 报错


🌆字符分类函数


🌃isdigit 十进制判断


🌃isxdigit 十六进制判断


🌃isupper 大写判断


🌃islower 小写判断


🌃toupper 转为大写


🌃tolower 转为小写


🌆内存函数


🌃memcpy 拷贝


🌃memmove 移动


🌃memcmp 比较


🌃memset 设置


🌇总结


🌇前言


eaa5a6b751c249ed81a615794b9bbe34.png

 这是牛客网上的一道简单题:判断输入字符是否为字母,一般的解决方法是通过ASCII码判断,不过这样做的话判断表达式较长,此时我们可以利用C语言中的库函数isalpha(判断是否为字母) 来完成这个题目,不仅代码量少,而且通俗易懂。要实现这种效果,就需要学习C语言中的各种库函数,而本文会列出大多数字符串函数和内存函数的使用及其实现,如果你想学习C语言库函数或对字符串、内存有好奇之心,不妨仔细来看看吧!🎉🎉🎉

此文章分为三部分:字符串函数、 字符分类函数、内存函数。

3b6315e98ac045e9823070764a4a5247.png

通过库函数简化后的代码

🌇正文

 首先我们从字符串函数开始介绍,顾名思义,字符串函数就是为字符串设计的函数,我们比较熟悉的有字符串长度统计函数 strlen、字符串比较函数 strcmp,除此之外还有很多实用的字符串函数,比如字符串追加、字符串分割、字符串寻找等等,话不多说,让我们直接进入主题:


3cab8a95305e49df8b406ed1507ef7ad.gif

🌆字符串函数

🌉长度不可控的字符串函数

下面介绍的是对目标字符串操作长度不可控的函数,使用场景相对有限。


🌃strlen 长度统计

 strlen 是用来统计字符串长度的一个库函数,因为字符串有个重要特征:以'\0'作为结束标志。strlen 正是根据这一特点,从首地址开始,逐个比对统计,得出此字符串的长度。当然如果没有结束标记,strlen 就会一直往后找,得出一个随机值。

022459603aec44b7a31198dcd3b78afb.png

这是 strlen 的标准格式


28fcfa91b10842f982cbb9b194a3c70f.png

strlen 计算字符串长度

 

使用注意事项:

  • strlen 统计的是 \0 之前出现的字符个数
  • 使用时,必须包含结束标志
  • 返回值是 size_t (unsigned int 无符号整型)
  • 112f5412beaa46e0b0173e0bb41b84b8.png模拟实现 strlen


 这个函数的工作原理我们已经清楚了,可以试着模仿库函数的方式,写出一个属于自己的 strlen。


 既然是模仿库函数,那么在返回类型、参数类型等方面要和库函数一致,在统计长度前,我们可以使用一个临时指针记录起始位置,当我们的源指针指向结束标志时,循环结束,将两个指针相减,就能得到元素个数(关于指针 - 指针得到元素个数) ,也就是字符串长度。下面来看看具体代码实现吧:

//strlen 计算字符串长度
size_t myStrlen(const char* p)
{
  assert(p);//断言,防止空指针
  char* tmp = p;//记录起始位置
  while (*p)
  {
    p++;//在循环内指向+1操作,避免位置出错
  }
  return (size_t)(p - tmp);//指针 - 指针得到元素个数
}
int main()
{
  char* pa = "Hello World!";
  //size_t len = strlen(pa);
  //printf("库函数实现结果:\n%zu\n", len);
  size_t len = myStrlen(pa);//此时调用我们写的函数
  printf("模拟函数实现结果:\n%zu\n", len);
  return 0;
}

 同样的,我们使用之前的示例来验证此函数的可行性 ,可以看到结果与库函数一致。

98841249671e448d86d077c55b4d1a60.png

🌃strcpy 拷贝


字符串拷贝需要两个字符串,字符数组 dest(目标) 与字符串 src(源),strcpy 中只需要这两个参数,即把源字符串内容拷贝到目标字符数组中(源字符串中的结束标志也会拷贝),其中要确保字符数组  dest 可改变,数组空间也要足够大,不然装不下源字符串就尴尬了。


21027d40f7d846fc8fecf26635e26584.png

strcpy 标准格式

f487a968bee04855812ccbc23d2aaad0.png

使用注意事项:

  • 源字符串中必须包含 \0
  • 源字符串中的 \0 会拷贝到目标字符数组中
  • 目标空间必须足够大,能够装下源字符串
  • 目标空间必须是可修改的
  • 0969bb1a09764f2faa1041eeae9b6588.png
  • 模拟实现 strcpy


 同样的,我们可以对这个函数进行模拟实现,拷贝的本质就是赋值,当源字符串中的首元素拷贝到目标字符数组中后仍然位于首位置,也就是说两个字符串元素拷贝位置是同步的,既然源字符串中的结束标志也要拷贝过去,那么我们就可以将其和赋值写进一个循环判断条件中(这样会构成一段非常奇妙的代码),这样一来我们整个程序的可读性就很不错了。

//strcpy 字符串拷贝
char* myStrcpy(char* dest, const char* src)
{
  assert(dest && src);//断言
  char* tmp = dest;//记录起始位置
  //当*src 为结束标志并赋给 *dest时,整体为假,
  //循环终止,目标数组也拿到了结束标志
  while (*dest++ = *src++)
  {
    ;//空语句
  }
  return tmp;//返回起始地址
}
int main()
{
  char arr1[20] = "xxxxxxxxx";
  char arr2[] = "Hello!";
  //printf("库函数实现结果:\n%s\n", strcpy(arr1, arr2));
  printf("模拟函数实现结果:\n%s\n", myStrcpy(arr1, arr2));
  return 0;
}

当然,我们的模拟函数也能实现需求

805be971ff25498a85a75e1e6866f8e5.png

🌃strcmp 比较

 字符串比较函数是从首字符开始比较,通过字符对应的ASCII码值对比,就能知道大小,如果str1>str2,返回1,如果str1<str2,返回-1,如果两个字符串相等,则返回0。因为比较并不需要改变值,所以使用常量字符串也能比较。

52a64b764d8047ed810a2bc24fd4ede9.png

strcmp 标准格式

72c04ba512c64196a2b2834fb3e07ecc.png

strcmp 的返回值

6345e21d3c4d44d9941c4297eec55f2f.png

使用注意事项:

  • 字符串大小比较时,与长度无关
  • 从首字符开始,逐字符比较
  • 通过字符对应的ASCII码值做对比
  • 86780521a3b94766b53534f7c69c47f7.png
  • 模拟实现 strcmp

 我们可以通过指针的移动来模拟实现这个函数,即指向 str1 的指针 dest、指向 str2 的指针 src,对两个指针解引用后的值进行比较,如果相同就同时向后偏移,直到找到不同的值或移动到结束标志处停止,最后再分情况确定返回值就行了。

//strcmp 字符串比较
int myStrcmp(const char* dest, const char* src)
{
  assert(dest && src);//断言
  //当找到不同数或移动到\0处时,循环停止
  while (*dest == *src && (*dest || *src))
  {
    dest++;//没有找到不同的元素
    src++;//需要向后偏移
  }
  //分情况判断,确定返回值
  if (*dest - *src > 0)
    return 1;
  else if (*dest - *src < 0)
    return -1;
  else
    return 0;
}
int main()
{
  char* str1 = "BATZ";
  char* str2 = "BAT";
  printf("库函数实现结果:\n%d\n", strcmp(str1, str2));
  //printf("模拟实现函数结果:\n%d\n", myStrcmp(str1, str2));
  return 0;
}

 使用模拟函数通过测试用例:

87ad5e006002462997c025c16d723e99.png

🌃strcat 追加

 追加,就是在目标字符数组的末尾(\0处)添加源字符串的值,比如目标字符串数组中为abcd,源字符串为1234,经过追加后,字符数组就变为了abcd1234。值得一提的是,strcat 无法自己给自己追加,因为在追加过程中,目标字符数组结束标志会被覆盖掉,导致源字符串(其实就是目标字符,因为是自己给自己追加)中的结束标志也消失了,追加过程会无法停止。

1cef557cf3d14943bc8068c5a9e3d3cd.png

strcat 标准格式

d0497235f9bb435fb99d520f8efd1766.png

使用注意事项:

  • 源字符串和目标字符数组中都必须有\0
  • 目标空间必须足够大
  • 目标空间必须可修改,所以是字符数组
  • 93ab5d6485e64bcdb88a9bc48f43e54b.png
  • 模拟实现 strcat


 既然是在目标字符数组的末尾处追加字符,就需要把指向首地址处的指针 dest 移向尾地址,当然在移动前要保存此地址,将此时的尾地址看作首地址2,将源字符串中的元素从此处开始拷贝至目标字符数组中,这样就完成了追加的操作,最后再返回之前记录的首地址就行了。

//strcat 字符串追加
char* myStrcat(char* dest, const char* src)
{
  assert(dest && src);//断言
  char* tmp = dest;//记录目标字符数组首地址
  while (*dest)
  {
    dest++;//将指针dest移动至尾元素处
  }
  //类似 strcpy 拷贝操作
  while (*src)
  {
    //判断条件用 *src就行了
    *dest++ = *src++;//确保源字符串中的每个元素都能追加上
  }
  return tmp;//返回首地址
}
int main()
{
  char arr1[20] = "ABCD";
  char arr2[] = "1234";
  //printf("库函数实现结果:\n%s\n", strcat(arr1,arr2));
  printf("模拟函数实现结果:\n%s\n", myStrcat(arr1, arr2));
  return 0;
}

b7fbbe4aa34544d9822c0933c87fe401.png

🌃strncmp 可控比较

 同样的,strncmp 也能控制比较长度,当然控制长度不能超过源字符串的长度,不然是无意义的

1ada5821d3cc46e58e3f32213d5176a6.png

strncmp 标准格式

c8557473e10a45d7aabf114b5304196e.png

返回值与 strcmp 完全一致

722b515ad11f4a83a56a02b8b4854d04.png

使用注意事项:

  • 与 strcmp 基本一致
  • 控制比较字节数不能为负数
  • 8ee0a476338340ec99353c63973b470a.png
  • 模拟实现 strncmp

 这个模拟实现也比较简单,大体思路与 strcmp 的模拟一样,只是循环判断条件变为了 k 和 *dest (当对比到结束标志处,循环也会停止)。

//strncmp n个字符串比较
int myStrncmp(const char* dest, const char* src, size_t k)
{
  assert(dest && src);//断言
  //为何使用前置--?
  //因为这样能有效避免多判断一次的情况
  while (--k && *dest == *src)
  {
    dest++;//确保每位都能对比到
    src++;
  }
  //分情况返回
  if (*dest - *src > 0)
    return 1;
  else if (*dest - *src < 0)
    return -1;
  else
    return 0;
}
int main()
{
  char* str1 = "BATZ";
  char* str2 = "BAT";
  //printf("库函数实现结果:\n%d\n", strncmp(str1, str2, 3));
  printf("模拟函数实现结果:\n%d\n", myStrncmp(str1, str2, 3));
  return 0;
}

7c47796174134aa29f7448133c9ca000.png

🌃strncat 可控追加

 可控追加,旨在控制源字符串中字符追加数,比如目标字符数组为abcd,源字符串为1234,我们传递字节数为2,当追加结束后,目标字符数组变为abcd12,同所有可控家族成员一样,strncat 也会自动添加结束标志 \0。因此利用 strncat 自己给自己追加,能够很好的完成任务,避免结束标志吞噬问题。

1446abcbce694a1b803fa5a903c288da.png

strncat 标准格式

739533284aff4282932bf75f6c177552.png

使用注意事项:

  • 目标字符数组中必须有\0
  • 目标空间必须足够大
  • 目标空间必须可修改
  • 源字符串中可以不包含\0
  • e4addafa106a41c58831c1cf6893169d.png
  • 模拟实现 strncat

 代码大体与模拟 strcat 的一致,只不过有两个地方需要注意:1.循环判断条件 2.最后 \0 的添加

//strncat n个字符串追加
char* myStrncat(char* dest, const char* src, size_t k)
{
  assert(dest && src);//断言
  char* tmp = dest;//记录起始位置
  //使指针移向目标字符数组的末尾处
  while (*dest)
  {
    dest++;
  }
  //判断条件为k,即传入的控制字节数
  while (k--)
  {
    *dest++ = *src++;//确保每个元素都能追加
  }
    *dest = '\0';
  return tmp;//返回目标字符数组的起始地址
}
int main()
{
  char arr1[20] = "xxxxxxx";
  char arr2[] = "Hello World!";
  //printf("库函数实现结果:\n%s\n", strncat(arr1, arr2, 5));
  printf("模拟函数实现结果:\n%s\n", myStrncat(arr1, arr2, 5));
  return 0;
}

5f0c8d56df694bd59330487755e14327.png

🌉特殊字符串函数

🌃strstr 寻找

 字符串寻找函数,作用是在目标字符串中寻找是否出现过源字符串,如果出现则返回第一次其在目标字符串中第一次出现的地址,如果没有出现,则返回一个空指针。

0a4b07591872488cb2d6e5adbabadc27.png

ststr 标准格式

c45b4b63d7f64447ba4340edc450b590.png

使用注意事项:

  • 只要传入的字符串地址就行了
  • 这个函数没有什么需要特别注意的事项

611686e6cb3440f8a4d3c38dd5484c74.png


目录
相关文章
|
4天前
|
缓存 安全 编译器
【C 言专栏】C 语言函数的高效编程技巧
【5月更文挑战第1天】本文探讨了C语言中函数的高效编程技巧,包括函数的定义与作用(如代码复用和提高可读性)、设计原则(单一职责和接口简洁)、参数传递方式(值传递、指针传递和引用传递)、返回值管理、调用约定、嵌套与递归调用,以及函数优化技巧和常见错误避免。掌握这些技巧能提升C语言代码的质量和效率。
【C 言专栏】C 语言函数的高效编程技巧
|
4天前
|
存储 C语言 开发者
【C言专栏】C 语言实现动态内存分配
【4月更文挑战第30天】C语言中的动态内存分配允许程序运行时按需分配内存,提供处理未知数据量的灵活性。这涉及`malloc()`, `calloc()`, `realloc()`, 和 `free()`四个标准库函数。`malloc()`分配指定大小的内存,`calloc()`同时初始化为零,`realloc()`调整内存大小,而`free()`释放内存。开发者需谨慎处理内存泄漏和指针使用,确保程序的稳定性和性能。动态内存分配是C语言中的重要技能,但也需要良好的内存管理实践。
|
5天前
|
存储 C语言
C语言进阶---------作业复习
C语言进阶---------作业复习
|
5天前
|
存储 Linux C语言
C语言进阶第十一节 --------程序环境和预处理(包含宏的解释)-2
C语言进阶第十一节 --------程序环境和预处理(包含宏的解释)
|
5天前
|
自然语言处理 Linux 编译器
C语言进阶第十一节 --------程序环境和预处理(包含宏的解释)-1
C语言进阶第十一节 --------程序环境和预处理(包含宏的解释)
|
5天前
|
存储 编译器 C语言
C语言进阶第十课 --------文件的操作-1
C语言进阶第十课 --------文件的操作
|
5天前
|
存储 程序员 C语言
C语言进阶第九课 --------动态内存管理-2
C语言进阶第九课 --------动态内存管理
|
5天前
|
编译器 C语言
C语言进阶第九课 --------动态内存管理-1
C语言进阶第九课 --------动态内存管理
|
5天前
|
C语言
C语言进阶第八课 --------通讯录的实现
C语言进阶第八课 --------通讯录的实现
|
7天前
|
C语言
pta浙大版《C语言程序设计(第3版)》 习题6-4 使用函数输出指定范围内的Fibonacci数 (20分)
pta浙大版《C语言程序设计(第3版)》 习题6-4 使用函数输出指定范围内的Fibonacci数 (20分)