深入C语言库:字符与字符串函数模拟实现

简介: 深入C语言库:字符与字符串函数模拟实现

前言

C语言的库函数,是我们经常在编写程序所用到的函数,我们可以借用库函数去实现各种各样的功能,在本篇文章,我们介绍的是C语言中字符串和字符的相关库函数,以及他们的模拟实现,通过模拟实现我们可以深入了解到库函数的工作原理,以便今后更好的使用。


我们在学习C语言的过程中,除了使用最多的头文件<stdio.h>,还会使用其他头文件,利用其中的库函数帮助我们简化代码的过程,比如像<math.h>,<string.h>等头文件,而今天带大家详细了解一下<string.h>吧。


一、C语言相关字符串函数一览表

求字符串长度

strlen
长度不受限制的字符串函数 strcpy   strcat   strcmp
长度受限制的字符串函数 strncpy  strncat  strncmp
字符串查找 strchr strstr  strtok
错误信息报告 strerror
内存操作函数 memcpy  memmove  memset  memcmp


二、strlen函数(求字符串长度)


2.1 用法

1.声明:size_t strlen(const char *str)


  • str – 要计算长度的字符串。

2.作用:计算字符串 str 的长度,直到空结束字符(‘\0’),但不包括空结束字符。


3.返回值:该函数返回字符串的长度


2.2 实例

#include <stdio.h>
#include <string.h>
int main()
{
  char* str1 = "abcd";
  int ret = strlen(str1);
  printf("%d", ret);
  return 0;
}


运行结果为4


我们看到这就是strlen函数的功能,即求字符串的长度,这里的长度是不算‘\0’的,所以求一个字符数组的长度时,一定要规定‘\0’的位置。这里求出来的字符串长度就是我们可以看到的这4个字符


注意事项:


  1. 字符串已经 '\0' 作为结束标志,strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包含 '\0' )。  
  2. 参数指向的字符串必须要以 '\0' 结束。
  3. 注意函数的返回值为size_t,是无符号的。


2.3 模拟实现

2.3.1 计数法

思路:我们可以用一个指针变量p指向首元素和一个计数变量count并初始化为0,然后循环解引用指针所指向的元素,判断这个元素是否为‘\0’,不是每次p++,count++,是就跳出循环,返回count。


#include<stdio.h>
int my_strlen(char* p)
{
  int  count = 0;
  while (*p)//当指向'\0',也就是0,为假跳出循环
  {
    p++;//指向下一个元素
    count++;//计数
  }
  return count;
}
int main()
{
  char arr[] = "abcdef";
  int len = my_strlen(arr);//计算arr字符串的长度
  printf("%d\n", len);
  return 0;
}

2.3.2 指针-指针

思路:首先定义两个指针p1,p2,让两个指针指向首元素,然后让一个指针p2循环++,直到指向‘\0’就停止,最后返回p2-p1


首先大家要清楚指针-指针是指在同一空间内,两个指针之间的元素个数。


int my_strlen(char* p1)
{
  char* p2 = p1;//使两个指针都指向首元素
  while (*p2)
  {
    p2++;
  }
  return p2 - p1;//返回两指针直接的元素的个数就是其长度
}
int main()
{
  char arr[] = "abcdef";
  int len = my_strlen(arr);//计算arr字符串的长度
  printf("%d\n", len);
  return 0;
}


2.3.3 递归法

思路:假设我们要计算字符串“abcdef”的长度,我们可以拆分为1+“bcdef”的长度,同理“bcdef”的长度可以拆分为1+“cdef”的长度…理解了这个思路,我们就可以实现递归,首先定义一个指针变量p,如果p!=‘\0’,我们就把p+1作为参数调用本函数,直到p为0.


int my_strlen(char* p)
{
  if (*p != '\0')
  {
    return 1 + my_strlen(p + 1);//每次调用p+1指向下一个元素
  }
  else
  {
    return 0;//结束递归
  }
}
int main()
{
  char arr[] = "abcdef";
  int len = my_strlen(arr);//计算arr字符串的长度
  printf("%d\n", len);
  return 0;
}


三 、strcpy函数(字符串拷贝)

3.1 用法

  • 声明:char *strcpy(char *dest, const char *src),dest – 指向用于存储复制内容的目标数组,src – 要复制的字符串。
  • 作用:把 src 所指向的字符串复制到 dest。需要注意的是如果目标数组 dest 不够大,而源字符串的长度又太长,可能会造成缓冲溢出的情况。
  • 返回值:该函数返回一个指向最终的目标字符串 dest 的指针。

3.2 实例

#include <stdio.h>
#include <string.h>
int main()
{
  char str1[20] = "abcdef";
  char str2[] = "kpl";
  strcpy(str1, str2);
  printf("%s", str1);
  return 0;
}

运行结果是:kpl

我们看到strcpy函数在这里的作用就是把str2的内容拷贝到了str1中。

注意事项:

  1. 源字符串必须以 '\0' 结束。(没有'\0'strcpy函数就不知道什么时候拷贝结束了)
  2. 会将源字符串中的 '\0' 拷贝到目标空间。
  3. 目标空间必须足够大,以确保能存放源字符串。(防止造成越界访问)
  4. 目标空间必须可变。(目标空间不能被const修饰


3.3 模拟实现

思路:我们想要将src的内容拷贝进des中,首先src的内容不能被改变,且保证都不是空指针。

利用循环把str2的内容拷贝到字符串str1中,这里简化了代码,把++放到了赋值语句中,这里需要注意我们要把++放到dest以及src的后面,这样保证先正常赋值第一个字符,再向后推移继续拷贝,直到赋值完‘\0’,条件为假,跳出循环。

#include<stdio.h>
#include<string.h>
char* ma_strcpy(char* dest,const char* src)
{
    char* temp = dest;
    while(*dest++ =*src++)
    {
        ;
    }
    return temp;
}
int main()
{
    char str1[] = "abcdef";
    char str2[] = "kpl";
    char* ret = my_strcpy(str1,str2);
    printf("%s", ret);
    return 0;
}

四、strcat函数(字符串连接)

4.1 用法

声明:char *strcat(char *dest, const char *src)


  • dest – 指向目标数组,该数组包含了一个 C 字符串,且足够容纳追加后的字符串。
  • src – 指向要追加的字符串,该字符串不会覆盖目标字符串。

作用:把 src 所指向的字符串追加到 dest 所指向的字符串的结尾。


返回值:该函数返回一个指向最终的目标字符串 dest 的指针。


4.2 实例

#include <stdio.h>
#include <string.h>
int main()
{
  char str1[20] = "abcd";
  char str2[] = "efgh";
  char* ret = strcat(str1, str2);
  printf("%s", ret);
  return 0;
}

输出结果:abcdefgh


我们看到strcat函数就是把str2的内容追加到了str1的末尾,从str1的‘\0’开始追加字符串str2的内容。


4.3 模拟实现

思路:


利用while循环把dest指针指向字符串末尾的'\0',因为第二个字符串追加到目标字符串是从目标字符串的'\0'开始追加的。


利用循环从dest的末尾位置开始,把src对应的地址内容赋值给目标字符串,两个地址都是后置++,先正常把str2的第一个字符赋值给str1,再++向后推移继续将str2中的字符赋值给目标字符串。


代码实现:


char* my_strcat(char* dest, const char* src)//防止src的值被改变
{
  assert(dest && src);//不能为空指针
  char* ret = dest;
  while (*dest)//使dest指向末尾
  {
  dest++;
  }
  while (*dest++ = *src++)//循环赋值
  {
  ;
  }
  return ret;
}
int main()
{
  char dest[20] = "hello ";
  char src[20] = "world!";
  my_strcat(dest, src);
  printf("%s", dest);
  return 0;
}


五、strcmp函数(字符串比较)

5.1 用法

1. 声明:int strcmp(const char* str1,const char*str2)


  • str1 – 要进行比较的第一个字符串。
  • str2 – 要进行比较的第二个字符串。

2. 作用:strcmp() 会根据 ASCII 编码依次比较 str1 和 str2 的每一个字符,直到出现不到的字符,或者到达字符串末尾(遇见‘\0’)


3. 返回值:


  • 如果返回值小于 0,则表示 str1 小于 str2。
  • 如果返回值大于 0,则表示 str1 大于 str2。
  • 如果返回值等于 0,则表示 str1 等于 str2。


5.2 实例

strcmp用于比较字符串,并返回>0,==0,<0的值,让我们看看他的具体使用吧


#include <stdio.h>
#include <string.h>
int main()
{
  char str1[] = "abcd";
  char str2[] = "abc";
  int ret = strcmp(str1, str2);
  printf("%d", ret);
  return 0;
}

输出结果为1


如果:


char str1[] = "abc";
char str2[] = "abc";

输出结果为0


如果:


char str1[] = "abc";
char str2[] = "abcd";

输出结果为-1


5.3 模拟实现

思路:从两个字符串的首元素开始比较,先判断字符串str1是不是空如果是空直接就返回0,如果两个字符相等,两个字符串首地址进行++操作后移继续比较,比到最后一个字符串如果还是相等返回最后两个字符对应ASSIC码值相减后的结果,因为两个字符串相等所以结果就是 0 。


如果在比较过程中就发现了两组字符存在大小差异,即结束判断,返回两个字符对应ASSIC相减的结果,>0就是str1大于str2,<0就是str1小于str2。


代码示例如下:


#include<assert.h>
int my_strcmp(const char* str1, const char* str2)
//经const修饰让*str1与*str2无法改变
{
  assert(str1 && str2);//判断str1和str2是否为空指针
  //空指针直接报错,头文件<assert.h>
  while (*str1== *str2)
  {
  if (*str1 == '\0')
  {
    return 0;
  }
  str1++;
  str2++;
  }
  return *str1 - *str2;
}
int main()
{
  char str1[] = "abcd";
  char str2[] = "Abcd";
  int ret = my_strcmp(str1, str2);
  printf("%d\n", ret);
  return 0;
}


六、strncpy函数

6.1 用法

声明:char *strncpy(char *dest, const char *src, size_t n)


  • dest – 指向用于存储复制内容的目标数组。
  • src – 要复制的字符串。
  • n – 要从源中复制的字符数。

作用: 把 src 所指向的字符串复制到 dest,最多复制 n 个字符。当 src 的长度小于 n 时,dest 的剩余部分将用‘\0’填充。


6.2 实例

#include <stdio.h>
#include <string.h>
int main()
{
  char str1[20] = "abcd";
  char str2[] = "kpl";
  char* ret = strncpy(str1, str2, 2);
  printf("%s", ret);
  return 0;
}

输出结果为:kpcd


我们看到我们在strncpy函数中,前两个参数是字符串的地址,第三个参数我们写的是 2 ,所以就是把字符串str2的前两个字符拷贝到str1中。  


6.3 模拟实现

思路:这里的模拟实现只有在循环的位置(①和②的位置)进行了稍微的改变,把我们的需要进行拷贝的元素个数作为循环的变量, 拷贝几个字符,循环几遍就可以了,其他代码和strcpy的编写是一样的。


代码示例如下:


char* my_strncpy(char* dest, const char* src, int n)
{
  assert(dest && src);
  char* ret = dest;    //将dest首地址储存在ret中,方便返回
  while (*src && n)
  {
  *dest = *src;
  dest++;
  src++;
  n--;
  }
  if (n != 0)//n>src
  {
  while (n)
  {
    *dest = '\0';
    dest++;
    n--;
  }
  }
  return ret;  //返回数组的首地址
}


七、strncat函数

7.1 用法

声明:char *strncat(char *dest, const char *src, size_t n)


  • dest – 指向目标数组,该数组包含了一个 C 字符串,且足够容纳追加后的字符串,包括额外的空字符。
  • src – 要追加的字符串。
  • n – 要连接的最大字符数。

用法:把 src 所指向的字符串追加到 dest 所指向的字符串的结尾,直到 n 字符长度为止。

如果n<src的长度,将source指向字符串的前n个字符追加到dest指向的字符串末尾,再追加⼀个 \0 字符。


如果n>=src,只会将字符串中到\0 的内容追加到dest指向的字符串末尾


7.2 实例

#include <stdio.h>
#include <string.h>
int main()
{
  char str1[20] = "abcd";
  char str2[] = "efgh";
  char* ret = strncat(str1, str2, 2);
  printf("%s", ret);
  return 0;
}

输出结果为abcdef


我们上图看到的代码在strncat中前两个参数是字符串的地址,第三个参数是拷贝的字符个数,这里写的是2所以把str2前两个字符拷贝到str1中


注意:如果n<src时,要在末尾自动补充’\0’。


7.3 模拟实现

代码示例如下:


#include<assert.h>
char* my_strncat(char* dest, const char* src, int n)
{
  char* ret = dest;  //将dest首地址储存在ret中
  assert(dest&&src);  //保证dest、src非空
  while (*dest != '\0')//找到dest结尾的‘\0’
  {
  dest++;
  }
  while (n && (*dest++ = *src++))//把src里的字符一个个放入dest后
  {
  n--;   //循环跳出条件
  }
  if(n==0)
  {
  *dest = '\0'; //如果n<src
  }
  return ret; //返回dest字符串起始地址
}


八、strncmp函数

8.1 用法

声明:int strncmp(const char *str1, const char *str2, size_t n)


  • str1 – 要进行比较的第一个字符串。
  • str2 – 要进行比较的第二个字符串。
  • n – 要比较的最大字符数。

作用: 把 str1 和 str2 进行比较,最多比较前 n 个字符

返回值:

如果返回值 < 0,则表示 str1 小于 str2。

如果返回值 > 0,则表示 str1 大于 str2。

如果返回值 = 0,则表示 str1 等于 str2。


8.2 实例

#include<stdio.h>
#include<string.h>
int main()
{
  char arr1[20] = "abcdef";
  char arr2[20] = "abcddd";
  int ret1 = strncmp(arr1, arr2, 3);//比较前三个字符
  int ret2 = strncmp(arr1, arr2, 8);//即使给出的num太大,遇见'\0'也会停止
  int ret3 = strncmp(arr1, arr2, 6);//比较全部字符
  printf("%d %d %d\n", ret1, ret2,ret3);
  return 0;
}

输出结果为:0 1 1。


8.3 模拟实现

代码示例如下:


#include <stdio.h>
#include<assert.h>
int my_strncmp(const char* str1, const char* str2, size_t n)
{
  assert(str1 && str2);
  while (n-- && *str1 == *str2)//任意一个条件不满足就跳出循环
  {
  if (*str1 =='\0')
  {
    return 0;
  }
  str1++;
  str2++;
  }
  return *str1 - *str2;
}


九、strchr函数(字符串查找字符函数)

9.1 用法

声明:char *strchr(const char *str, int c)


  • str – 要查找的字符串。
  • c – 要查找的字符。

作用:在参数 str 所指向的字符串中搜索第一次出现字符 c(一个无符号字符)的位置。


返回值:如果在字符串 str 中找到字符 c,则函数返回指向该字符的指针,如果未找到该字符则返回 NULL。


9.2 实例

#include<string.h>
int main()
{
  char arr[20] = "hello betty";
  char* p = strchr(arr,'y');
  if (*p == NULL)
  {
  printf("没找到\n");
  }
  else
  {
  printf("找到了\n");
  }
  return 0;
}

9.3 模拟实现

思路:一样先排查空指针,然后循环寻找,如果寻找到,直接返回其地址。找不到就返回空指针NULL


代码实现:


char* my_strchr(const char* str, int c)
{
  assert(str);//排查空指针
  while (*str)
  {
  if (*str == c)
  {
    return str;//找到返回其地址
  }
  str++;
  }
  return NULL;//找不到返回空指针
}
int main()
{
  char arr[20] = "hello betty";
  char* p =my_strchr(arr,'y');
  if (*p == NULL)
  {
  printf("没找到\n");
  }
  else
  {
  printf("找到了\n");
  }
  return 0;
}

十、strstr函数(字符串查找小字符串函数)

10.1 用法

声明:char *strstr(const char *haystack, const char *needle)


  • haystack – 要被检索的 C 字符串。
  • needle – 在 haystack 字符串内要搜索的小字符串。

作用:在字符串 haystack 中查找第一次出现字符串 needle 的位置,不包含终止符 ‘\0’。


返回值:该函数返回在 haystack 中第一次出现 needle 字符串的位置,如果未找到则返回 空指针(NULL)


10.2 实例

#include <stdio.h>
#include <string.h>
int main()
{
  char str1[] = "abcdefdgh";
  char str2[] = "d";
  char* ret = strstr(str1, str2);
  printf("%s", ret);
  return 0;
}

输出结果为defdgh


这里我们看到strstr中接收的是我们两个字符串的地址,定义了一个字符指针变量ret去接收函数返回的地址,我们看到str2中只有一个字符d,我们在str1进行查找看是否也有字符串d,明显我们看到str1中第四个字符是d,所以停止查找返回第一次见到字符d所对应的地址,最后打印出来的是str1中字符d第一次出现后的地址所对应内容。


注意str2一定要是str1的子字符串(连续),这样才可以查找,否则是找不到的,查找不到就会返回空指针。


10.3 模拟实现

思路:首先用是用s1,s2指向两个字符串的首元素,用p记录str1中开始比较的元素的位置,方便重新开始比较。然后循环比较,如果*s1!=*s2,或者遇见‘\0’,就跳出循环,判断,如果是s2为‘\0’,说明配对成功,s1为‘\0’,则说明后续长度不够,匹配失败啦,除开以上情况,就让p++,重复上述流程,直到*p==‘\0’


在acbcef中找acb


代码实现如下:

#include <stdio.h>
char* my_strstr(const char* str1, const char* str2)
{
  assert(str1 && str2);//防止空指针
  const char* s1 = str1;
  const char* s2 = str2;
  const char* p = str1;//记录初始位置
  while (*p)
  {
    s1 = p;//从记录位置开始比较
    s2 =str2;
    while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2)
    {
      s1++;
      s2++;
    }
    if (*s2 == '\0')//配对成功
    {
      return (char*)p;//p原本被const修饰
 
    }
    else if (*s1 == '\0' && s2 != '\0')//s1后续字符少于s2
    {
      return NULL;
    }
    p++;//记录下一个位置
  }
  return NULL;
}
int main()
{
  char haystack[20] = "hello betty";
  char needle[10] = "betty";
  char* ret =my_strstr(haystack, needle);
  if (ret == NULL)
  {
    printf("未找到\n");
  }
  else
  {
    printf("找到啦,子字符串是:%s\n", ret);
  }
  return 0;
}


十一、strtok函数 (字符串分割函数)

11.1 用法

声明:char *strtok(char *str, const char *delim)


  • str – 要被分解成一组小字符串的字符串。第一次调用 strtok() 时,这个参数应该是你想要分割的字符串。随后的调用应该将此参数设置为NULL,以便继续从上次的位置分割。
  • delim – 包含分隔符的 C 字符串。

作用:strtok() 用于将字符串分割成一系列的子串


返回值:该函数返回被分解的第一个子字符串,如果没有可检索的字符串,则返回一个空指针。


11.2 实例

#include <stdio.h>
#include <string.h>
int main()
{
  char str1[] = "by@bite.cn";
  char str2[200] = { 0 };
  char sep[] = "@.";
  strcpy(str2, str1);
  char* ret = NULL;
  for (ret = strtok(str2, sep); ret != NULL; ret = strtok(NULL, sep))
  {
    printf("%s ", ret);
  }
}


注意事项:


  1. sep参数是个字符串,定义了用作分隔符的字符集合
  2. 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标 记。
  3. strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注: strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容 并且可修改。)
  4. strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串 中的位置。
  5. strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标 记。
  6. 如果字符串中不存在更多的标记,则返回 NULL 指针。


十二、strerror(错误信息报告)

12.1 用法

声明:char *strerror(int errnum)


  • errnum – 错误号,通常是 errno。


作用:从内部数组中搜索错误号 errnum,并返回一个指向错误消息字符串的指针。strerror 生成的错误字符串取决于开发平台和编译器。

返回值:该函数返回一个指向错误字符串的指针,该错误字符串描述了错误 errnum。


12.2 实例

参考以下代码:

#include<string.h>
#include<errno.h>//错误码的头文件
int main()
{
  //每一个错误码对应一个错误信息
  printf("%s\n", strerror(0));
  printf("%s\n", strerror(1));
  printf("%s\n", strerror(2));
  printf("%s\n", strerror(3));
  return 0;
}

输出:


No error (没有错误)

Operation not permitted (操作不允许)

No such file or directory (没有这样的文件)

No such process (没有这样的进程)


十三、memcpy函数(内存拷贝函数)(内存不重叠)

13.1 为什么要内存造作函数

上方我们的拷贝函数strcpy和strncpy都是专门拷贝字符串的函数,所以只能拷贝字符串。除了字符函数和字符串函数,<string.h>中还有一类内存操作函数,如memset(),memcmp()等函数,他们在功能和某些字符串函数很像,但作用范围更广,除了作用于字符串外,还可以作用于int ,double等内置类型,但因为是以字节为单位改变,所以限制也很大。


13.2 用法

声明:void *memcpy(void *str1, const void *str2, size_t n)


  • str1 – 指向用于存储复制内容的目标数组,类型强制转换为 void* 指针。
  • str2 – 指向要复制的数据源,类型强制转换为 void* 指针。
  • n – 要被复制的字节数。


作用:从存储区 str2 复制 n 个字节到存储区 str1。


返回值:该函数返回一个指向目标存储区 str1 的指针。


13.3 实例

参考代码如下:


#include <stdio.h>
#include <string.h>
int main()
{
  int arr1[100] = { 1,2,3,4,5,6,7,8,9,10 };
  int arr2[100] = { 0 };
  int i = 0;
  memcpy(arr2, arr1, 10 * sizeof(int));
  for (i = 0; i < 10; i++)
  {
    printf("%d ", arr2[i]);
  }
  return 0;
}

输出结果为:1 2 3 4 5 6 7 8 9 10


我们看到这里就是把arr1中的10个元素拷贝到了arr2中,memcpy的三个参数分别是两个数组的首地址,和数组arr1的待拷贝元素个数的大小(单位:字节)


注意事项:


  1. 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
  2. 这个函数在遇到 '\0' 的时候并不会停下来。
  3. 如果source和destination有任何的重叠,复制的结果都是未定义的。


13.4 模拟实现

思路:


  1. 定义一个void*类型的指针用来存放目标字符串,便于一会拷贝完成后,返回其地址
  2. 以元素的大小作为循环的条件,由于我们不知道元素的具体类型,所以我们采用最细致的方法进行元素的访问,每次-1个字节去访问元素的每一位字节
  3. 我们知道char*类型一次可以访问1个字节,这是最细致的访问方法,所以我们把我们的目标字符串和待拷贝的字符串地址都强制类型转换为char*类型,这样便于我们遍历整个元素,这样一个字节一个字节的把str2的内容拷贝给str1
  4. 我们把dest和src强制类型转换为char*类型后,每+1就是跳过一个字节,这样保证每一位都可以进行交换,保证元素交换成功

代码实现如下:


void* my_memcpy(void* dest, const void* src, size_t n)
{
  assert(dest && src);//防止空指针
  void* ret = dest;
  while (n--)
  {
  *(char*)dest = *(char*)src;
  dest = (char*)dest + 1;
  src = (char*)src + 1;
  }
  return ret;
}

我们在本程序中是把两个没有内存重叠的两个数组进行的相互拷贝,但是在内存出现重叠的情况时,memcpy函数在某些编译器下是无法实现的,所以我们在下面引入memmove函数。

十四、memmove函数(内存拷贝函数)(内存重叠)

14.1 用法

声明:void *memmove(void *str1, const void *str2, size_t n)


  • str1 – 指向用于存储复制内容的目标数组,类型强制转换为 void* 指针。
  • str2 – 指向要复制的数据源,类型强制转换为 void* 指针。
  • n – 要被复制的字节数。

作用:从 str2 复制 n 个字符到 str1,但是在重叠内存块这方面,memmove() 是比 memcpy() 更安全的方法。如果目标区域和源区域有重叠的话,memmove() 能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中,复制后源区域的内容会被更改。如果目标区域与源区域没有重叠,则和 memcpy() 函数功能相同。

返回值:该函数返回一个指向目标存储区 str1 的指针。


14.2 实例

#include <stdio.h>
#include <string.h>
int main() 
{
    char str[] = "Hello, World!";
    printf("Original string: %s\n", str);
    // 将字符串前6个字符移动到字符串的末尾
    memmove(str, str + 7, 6);
    printf("Modified string: %s\n", str);
    return 0;
}

输出结果:


Original string: Hello, World!

Modified string: World! World!


注意事项:


  1. 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
  2. 如果源空间和目标空间出现重叠,就得使用memmove函数处理。


14.3 模拟实现

思路:


1. 定义一个void*类型的指针变量用来存放目标字符串的地址,便于一会进行完拷贝操作后,返回拷贝后的目标字符串


2. 以字节的大小去作为循环变量,我们用最细致的方法,一个字节一个字节的进行拷贝,这样保证可以每一种数据类型都可以拷贝过去


3.我们的拷贝有两种情况,


  • 第一种情况:当dest的地址小于src的地址,证明我们需要把src的内容向左拷贝,为了可以保证拷贝的成功,我们的src需要从小到大去进行拷贝。我们把dest和src分别强制类型转换成char*,这样每+1就跳过一个字节,这样的拷贝方式是最细致的方法,适用于每一种数据类型,每一次跳过一个字节,最后跳过总的count字节数即可完成拷贝操作。
  • 第二种情况当dest的地址大于src的地址,我们把前面的元素向后拷贝,所以是把元素向右拷贝,这时为了保证地址重叠部分不被先行改变,所以先拷贝最后一个元素,倒着进行赋值,这样可以保证拷贝的数据准确不出错误。

因为我们情况二的拷贝需要倒着赋值,先赋值高地址的内容,所以我们先加上总字节数这样可以保证拷贝操作从最后一个元素进行,如上图的代码,我们需要进行拷贝的元素是4个整型元素,所以字节数是16个字节,因为进行了后置--操作,所以这时候的count已经是15,地址src和地址dest都是对应的拷贝位置的首地址,所以首地址+15对应的位置都是末元素的位置。


代码实现如下:


void* my_memmove(void* dest, const void* src, size_t n)
{
  assert(dest && src);//防止空指针
  void* ret = dest;
  if (dest <= src)//dest在src左侧
  {
  while (n--)
  {
    *(char*)dest = *(char*)src;
    dest = (char*)dest + 1;
    src = (char*)src + 1;
  }
  }
  else//dest在src的右侧
  {
  dest = (char*)dest + n - 1;//指向末尾
  src = (char*)src + n - 1;//指向末尾
  while (n--)
  {
    *(char*)dest = *(char*)src;
    dest = (char*)dest - 1;
    src = (char*)src - 1;
  }
  }
  return ret;
}


十五、memcmp函数(内存比较函数)

15.1 用法

声明:int memcmp(const void *str1, const void *str2, size_t n)


  • str1 – 指向内存块的指针。
  • str2 – 指向内存块的指针。
  • n – 要被比较的字节数。

作用:把存储区 str1 和存储区 str2 的前 n 个字节进行比较。


返回值:


  • 如果返回值 < 0,则表示 str1 小于 str2。
  • 如果返回值 > 0,则表示 str1 大于 str2。
  • 如果返回值 = 0,则表示 str1 等于 str2。


15.2 实例

#include <stdio.h>
#include <string.h>
int main()
{
  int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
  int arr2[] = { 1,3,2,4,5,6,7,8,9,10 };
  int ret = memcmp(arr1, arr2, 16);
  printf("%d", ret);
}

输出结果为-1


我们看到这里返回的数值是-1所以我们可以得到数组arr2是比数组arr1要大的,因为我们看到数组arr2[1]的元素要比arr1[1]的元素要大,这样终止了我们继续向后的比较,直接返回了一个小于0的数字。


15.3 模拟实现

思路:


  1. 定义两个void*指针存放需要进行比较的两个变量地址(这里由于不知道变量的类型,所以采用void*类型指针接收),以及一个无符号整型的count接收的需要进行比较的元素总大小(单位:字节)
  2. 利用总字节数作为循环的变量,循环完整个字节数即完成了所有元素的比较
  3. 以最细致的方法进行比较,把元素的地址类型转换为char*类型,char*类型每次可以访问一个字节,这样我们可以把元素进行逐一字节的比较
  4. 逐一比较每一个字节所对应的地址元素是不是相等,如果相等两个指针分别加1继续推移向下一个字节进行比较,如果两个元素存在大小的差异,直接返回这两个元素对应assic差异

代码实现如下:


#include<stdio.h>
#include<assert.h>
int my_memcmp(const void* str1, const void* str2, size_t n)
{
  assert(str1 && str2);//
  char* p1 = (char*)str1;
  char* p2 = (char*)str2;
  while (n--&&(*p1==*p2))
  {
  if (*p1 == '\0')
  {
    return 0;
  }
  p1++;
  p2++;
  }
  return *p1 - *p2;
}


15.4 strcmp,strncmp,memcmp之间的区别

  • memcmp是比较两个存储空间的前n个字节,即使字符串已经结束,仍然要比较剩余的空间,直到比较完n个字节。
  • strcmp比较的是两个字符串,任一字符串结束,则比较结束。
  • strncmp在strcmp的基础上增加比较个数,其结束条件包括任一字符串结束和比较完n个字节。
  • strcmp比较的字符串,而memcmp比较的是内存块,strcmp需要时刻检查是否遇到了字符串结束的 \0 字符,而memcmp则完全不用担心这个问题,所以memcmp的效率要高于strcmp


十六、memset函数(内存设置函数)

16.1 用法

声明:void *memcpy(void *str1, const void *str2, size_t n)


  • str1 – 指向用于存储复制内容的目标数组,类型强制转换为 void* 指针。
  • str2 – 指向要复制的数据源,类型强制转换为 void* 指针。
  • n – 要被复制的字节数。


作用:从存储区 str2 复制 n 个字节到存储区 str1。

返回值:该函数返回一个指向目标存储区 str1 的指针。


16.2 示例

#include <stdio.h>
#include <string.h>
int main()
{
  char str[] = "hello world";
  memset(str, 'x', 6);//以字节为单位
  printf(str);
  return 0;
}

输出结果:xxxxxxworld


int main()
{
  int arr[4] = { 1,2,3,4 };
  memset(arr, 1, sizeof(arr));
  int i = 0;
  for (i = 0; i < 4; i++)
  {
  printf("%d ", arr[i]);
  }
  return 0;
}

输出结果:16843009 16843009 16843009 16843009


16.3 模拟实现

思路:

  1. 判断dest地址的有效性
  2. 定义一个void*指针用来存放目标元素的地址方便一会返回其地址,由于不知道具体进行设置的元素类型,这里采用void*指针接收(void*指针可以接收任意类型的指针变量)
  3. 以总字节的大小作为循环的变量,循环完整个元素字节后就可以对整个元素都进行了设置
  4. 我们的机器都是小端存储的,所以我们对变量c进行一个强制类型的转换把变量c的地址强制类型转换为char*类型访问变量c的一个字节拿到的就是变量的有效位02,再把02初始化给目标元素,同样把目标元素也进行强制类型转换,把每一字节都进行初始化,每次向后+1直到循环了总字节的个数

代码实现如下:

void* my_memmove(void* dest, const void* src, size_t n)
{
  assert(dest && src);//防止空指针
  void* ret = dest;
  if (dest <= src)//dest在src左侧
  {
    while (n--)
    {
      *(char*)dest = *(char*)src;
      dest = (char*)dest + 1;
      src = (char*)src + 1;
    }
  }
  else//dest在src的右侧
  {
    dest = (char*)dest + n - 1;//指向末尾
    src = (char*)src + n - 1;//指向末尾
    while (n--)
    {
      *(char*)dest = *(char*)src;
      dest = (char*)dest - 1;
      src = (char*)src - 1;
    }
  }
  return ret;
}
相关文章
|
20天前
|
存储 算法 程序员
C语言:库函数
C语言的库函数是预定义的函数,用于执行常见的编程任务,如输入输出、字符串处理、数学运算等。使用库函数可以简化编程工作,提高开发效率。C标准库提供了丰富的函数,满足各种需求。
ly~
|
1月前
|
数据可视化 BI API
除了 OpenGL,还有哪些常用的图形库可以在 C 语言中使用?
除了OpenGL,C语言中还有多个常用的图形库:SDL,适合初学者,用于2D游戏和多媒体应用;Allegro,高性能,支持2D/3D图形,广泛应用于游戏开发;Cairo,矢量图形库,支持高质量图形输出,适用于数据可视化;SFML,提供简单接口,用于2D/3D游戏及多媒体应用;GTK+,开源窗口工具包,用于创建图形用户界面。这些库各有特色,适用于不同的开发需求。
ly~
156 4
|
1月前
|
C语言
C语言字符(串)函数
C语言字符(串)函数
|
23天前
|
存储 C语言
【c语言】字符串函数和内存函数
本文介绍了C语言中常用的字符串函数和内存函数,包括`strlen`、`strcpy`、`strcat`、`strcmp`、`strstr`、`strncpy`、`strncat`、`strncmp`、`strtok`、`memcpy`、`memmove`和`memset`等函数的使用方法及模拟实现。文章详细讲解了每个函数的功能、参数、返回值,并提供了具体的代码示例,帮助读者更好地理解和掌握这些函数的应用。
19 0
|
1月前
|
C语言
C语言常见字符函数和字符串函数精讲
C语言常见字符函数和字符串函数精讲
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
34 3
|
7天前
|
C语言
c语言调用的函数的声明
被调用的函数的声明: 一个函数调用另一个函数需具备的条件: 首先被调用的函数必须是已经存在的函数,即头文件中存在或已经定义过; 如果使用库函数,一般应该在本文件开头用#include命令将调用有关库函数时在所需要用到的信息“包含”到本文件中。.h文件是头文件所用的后缀。 如果使用用户自己定义的函数,而且该函数与使用它的函数在同一个文件中,一般还应该在主调函数中对被调用的函数做声明。 如果被调用的函数定义出现在主调函数之前可以不必声明。 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必多所调用的函数在做声明
22 6
|
27天前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
34 10
|
25天前
|
机器学习/深度学习 C语言
【c语言】一篇文章搞懂函数递归
本文详细介绍了函数递归的概念、思想及其限制条件,并通过求阶乘、打印整数每一位和求斐波那契数等实例,展示了递归的应用。递归的核心在于将大问题分解为小问题,但需注意递归可能导致效率低下和栈溢出的问题。文章最后总结了递归的优缺点,提醒读者在实际编程中合理使用递归。
53 7
|
25天前
|
存储 编译器 程序员
【c语言】函数
本文介绍了C语言中函数的基本概念,包括库函数和自定义函数的定义、使用及示例。库函数如`printf`和`scanf`,通过包含相应的头文件即可使用。自定义函数需指定返回类型、函数名、形式参数等。文中还探讨了函数的调用、形参与实参的区别、return语句的用法、函数嵌套调用、链式访问以及static关键字对变量和函数的影响,强调了static如何改变变量的生命周期和作用域,以及函数的可见性。
29 4