C语言中基础(有关数据拷贝的函数,例:strcpy,strncpy,memcpy,memove库函数的实现和应该注意的小细节)

简介: C语言中基础(有关数据拷贝的函数,例:strcpy,strncpy,memcpy,memove库函数的实现和应该注意的小细节)

一、今天我们讲一讲数据拷贝

首先肯定先得写一下我的校园生活,我们从昨天开始了我们的大学军训,给我的感觉(军训还是很有意义的,但是应该在刚开学的时候进行最后,现在 军训我还十分的不习惯,严重影响我的作息),今天5点50就起床(不够睡,难受的),然后要训练一天,只有中午两个小时和晚上一会可以进行自己的安排,时间上来说是非常宝贵的,所以我们进入话题


1.首先先讲一下strcpy函数的使用和自己实现:

(一.)首先是strcpy函数的使用方法,中文解释就是,复制拷贝的意思,就是把字符从一个地方复制拷贝到另一个地方,具体作用如下代码所示:

#include<stdio.h>
int main()
{
  char arr1[] = "abcdefghijk";
  char arr2[20] = "abc";
  strcpy(arr2, arr1);
  printf("%s",arr2);
  return 0;
}

8.png

以上就是strcpy函数的使用和输出结果,充分说明strcpy的作用就是把一个字符数组中的字符串给拷贝但另一个数组中(注意这个只是拷贝字符串的拷贝函数),不具有拷贝整形数组的功能。

(二.)现在我们就来看一下我们自己应该如何实现strcpy这个函数,我们先简述一下实现的原理和自己实现这个函数的时候的接收类型,就是在自己实现这个函数时应该用的格式char * strcpy ( char * destination, const char * source );这个就是我们的my_strcpy的函数接收类型,并且实现原理就是:我们从源头拷贝一份数据到目的地中如上述格式中的意思 从source到destination,但是有两个小注意点:

1.就是注意source前面有一个const,这个的意思及时保证我的源头的东西(就是我要拷贝的东西)是一个固定的值,是不会改变的值,所以为了避免源头被改变,所以我家一个const,此时就算我不小心改变了它,它也不会被改变

2.就是还要注意这个是字符型函数,所以应该用char * 类型的指针去接收,并且返回类型也是char * ;

所以具体实现和注意请看下列代码和相关注释:

#include<stdio.h>
#include<assert.h>
char* my_strcpy(char* dest, char* src)//所以我这可以用两个char*类型了指针来接收
{
  assert(dest && src);//这个的意思就是避免dest和src是空指针,(如果有了这个assert(断言),就可以使当它们其中之一有空指针的时候就会报错,避免程序运行不报错,但要注意引头文件)
  char* ret = dest;//写这步的好处和原因有两个 1.可以使我的dest发生改变的时候还有一个指针指向它,使我便与寻找 2.可以使我的返回类型变得更加完美,完美实现char*的返回值的目的
  while (*src)
  {
    *dest = *src;//这个的意思就是把源头的字符赋值给目的地
    dest++;//这两步一样就是使指针指向下一个字符,然后再循环
    src++;
  }
  *dest = *src;//这步的目的就是因为上面那个循环的条件是 src!='\0',所以当src为'\0'时,循环就会停止,导致*dest = *src这步在最后不能实现,所以'\0'就没有拷贝到dest中,所以我最后还要再进行一步赋值
  return ret;
}
int main()
{
  char arr1[] = "abcdef";
  char arr2[20] = "bcd";
  my_strcpy(arr2, arr1);//因为数组就是首元素的地址,所以这边传上去的其实就是两个地址
  printf("%s",arr2);
  return 0;
}

上述是最简单的按照正常思路进行的编码,这样就能成功实现strcpy这个函数了,下面展示一个它的进阶版本:

#include<stdio.h>
#include<assert.h>
char* my_strcpy(char* dest, char* src)//同上
{
  assert(dest && src);//同上
  char* ret = dest;//同上
  while (*dest++ = *src++);//这步的还是解引用后直接复制的意思,只是放在了循环之中(意思为当src为'\0'时,dest也为'\0',并且'\0'的ASCII码值为0,所以为假,所以此时循环依然停止)
  return ret;//同上
}
int main()
{
  char arr1[] = "abcdef";
  char arr2[20] = "bcd";
  my_strcpy(arr2, arr1);//同上
  printf("%s",arr2);
  return 0;
}

这个代码的具体原理与上述相同只是更加的取巧一些

2.看完strcpy的实现,现在我们看一下,strncpy的实现(具体原理相同,区别就在于这个n,代表的是固定字符拷贝的意思)

(一.)这边我们先首先看一下这个库函数的具体函数接收类型, 具体类型如下char * strncpy ( char * destination, const char * source, size_t num );

1.从这个接收类型,我们可以看出它与strcpy的区别仅在于这个size_t num,别的区别没有;

2.所以这个区别有什么区别呢?这里告诉大家,区别就在于我的这strncpy函数是可以控制我想要拷贝的字符串的元素个数的,不像是strcpy,只能复制一整个,我可以控制数量;

(二.)所以接下来然我们按照接收类型来尝试一下如何实现这个函数,请看以下代码和关键注释:


#include<stdio.h>
#include<assert.h>
char* my_strncpy(char* dest, char* src, size_t num)
{
  assert(dest && src);//同理
  char* ret = dest;
  while (num)
  {
    *dest = *src;  //复制
    src++;    //源地址往后+1
    dest++;
    num--;//我所需要拷贝的字符数随着我的循环一直减减
  }
  return ret;
}
int main()
{
  char arr1[] = "abcdefgh";
  char arr2[20] = " ";
  my_strncpy(arr2, arr1, 5);
  printf("%s", arr2);
  return 0;
}

这个就是strncpy函数的实现,其它原路与strcpy大致相同关键就在于那个while循环中的num的个数的循环,意思主要就是只要num不为0,我就会按照要求进行相应个数的拷贝

3.讲了上述的拷贝,大家有没有发现它的使用场景是非常的局限的,只能用于字符串之类的拷贝情形,所以接下来我们学一个,万能的内容拷贝函数(memcpy)

(一.)首先我们来看一下memcpy对整形数组的拷贝:

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

这个就是memcpy函数的基本使用,但是它不仅可以拷贝整形,它也可以拷贝结构体之类的strcpy不能拷贝的许多类型的数据

(二.)所以接下来我们看一下应该如何自己实现my_memcpy这个函数,代码如下:

#include<stdio.h>
void* my_memcpy(void* dest, const void* src, size_t num)//这个还是一个道理(不想写了),且因为源头数据是我要的(是不会被修改的,所以加一个const),这下我们把源头的数据拷贝到了目的地,最后返回目的地的地址,目的就是我们希望这个目的地的空间可以发生变化,所以返回类型我们就写void*
{
  void* ret = dest;
  assert(dest && src);//这边不敢又不会理解(原理就是因为下面传上来一个首元素地址,我这边可以用数组接,但是也可以用指针接,所以我这个位置的dest肯定是一个指针(类型是空指针,原理就是因为我这个函数的要求)所以毫无疑问不敢写成*dest,不然就是解引用,我拿到的就成了一个地址了,不是指针了,那么这个 dest!=NULL ,就不知道是什么鬼了)
  while(num--)//像这种有要求拷贝num个字节的函数(基本上就必须用到num--,这个表达式,因为只有这样才可以实现我按照num个的要求去实现函数嘛)
  //也可以写成int i=count; while(i--);一个道理的
  //下面这个东西就是可以让我的指针每次都向后移动一个字节(只有这样我才可以把每一个指针指向的字节都给拷贝过去)
  {
    *(char*)dest = *(char*)src;//这步的目的就是使我的两个指针是通过(char*)一个一个字节进行拷贝的,所以要把它们都给强制类型为char*指针,然后解引用(这样就是一个一个字符的拷贝)
      //下面这步要注意,因为如果是后置加加的话,后置的加加的优先级是比我的(强制类型转换是更高的),所以这边一定要把加加写成前置加加,(然后这边再说一遍,因为dest和src此时都是void*类型的指针,所以 不能直接写dest++和src++,因为空指针不能进行运算,所以要进行强制类型转换,然后才有前面那个优先级的问题)
    ++(char*)dest;//这个前置加加不要怕好吗,(意思就是先强制类型转换,再加加而已,谁让我是想让它一个一个字节走,没办法,所以要先强制类型转换再加加(如果后置加加的话就会导致,先进行了一步加加(使我的字节一下就跳了不止一个的字节(然后再进行一个字节一个字节这里就会出问题了))))
    ++(char*)src;//这样就实现了使指针向后走一步的操作(按照正常思路,此时可以让指针走一步了,那就应该要让它可以循环起来,所以要写一个循环出来,把它放到循环里面)
  }
  return ret;
}
int main()
{
  int i = 0;
  int arr1[] = { 1,2,3,4,5 };//但是要小心那种自己拷贝到自己的情况(因为这种情况会把前面那个字节内刚刚拷贝过去的内容给替换掉(就不是我原来那个需要的字节内容了,而是前前那个字节的内容,因为它把它替换掉了))
  int arr2[5] = { 0 };
  my_memcpy(arr2, arr1,sizeof(arr1));//这个是万能拷贝(字符,数组,数字,结构体,等等,都是可以拷的)
  for (i=0; i < 5; i++)
  {
    printf("%d ",arr2[i]);
  }
  return 0;
}

这个就是my_memcpy函数的实现,具体原理,注释非常明白

但是这边就又一个主要的问题:就是假如我是像把一个整形数组中的部分元素自己拷贝到自己部分元素的位置上,如果使用这个my_memcpy函数会是怎样一个情况呢?下面由图片说明问题:


9.png

这个图片就是当我想把一个数组中的元素复制到自己数组中不同位置元素是所发生的情况,举个形象的例子:就是 数组int arr[ ] = { 1,2,3,4,5,6,7,8,9,10 };我此时拷贝的是my_memcpy(arr + 2, arr, 20);把自己数组中的元素拷贝到 自己数组中,此时拷贝的内容就是如同上图所示,这个是有原因的,(原因就是当我要从数组前面开始向后拷贝的时候前面那个元素会把我后面我同时也需要拷贝的元素给覆盖掉,导致我需要的那个元素消失,此时当我再进行下一步拷贝是,此时拷贝的内容已经不再是我原来的内容,而是覆盖它的内容,所以就导致此时的第一个元素(1)会出现多次。


4.所以为了解决上述那个问题,我们就将引入本节最重要的一个代码(my_memove),这个代码就是用来解决数据拷贝时数据内容被覆盖的问题;

(一.)首先还是以int arr3[ ] = { 1,2,3,4,5,6,7,8,9,10 };my_memove(arr3+ 2,arr3, 20);为例,这边 我们需要画一个图解来方便我们理解;


10.jpeg

1.这个的意思就是当dest在src的左边时,此时我的src(3, 4,5, 6,7)就应该从前向后拷贝,而当我的dest在src的右边时,此时我的src(3, 4,5, 6,7)就应该从后向前拷贝,只有这样拷贝我的数据内容在拷贝过程中才不会被覆盖,而当我的dest超过src,此时从前向后还是从后向前都是可以的,所以按照上述原理,我就可以得到以下代码:

#include<stdio.h>
#include<assert.h>
void* my_memmove(void* dest, void* src, size_t count)//这边这个size_t是重新定义过的(这边也有另一个意思就是无符号的意思(所以说明这边这个数一定是正数))
{
  void* ret = dest;
  assert(dest && src);
  if (dest < src)
  {
    //从前向后
    while (count--)
    {
      *(char*)dest = *(char*)src;//数据交换,但是要注意点是要解引用和数据的强制类型转换,且这边的强制类型转换的目的是为了使我在拷贝数据时,是通过一个一个字节进行拷贝的,而不是4个4个,只有一个一个字节进行拷贝,这样我的拷贝内容才会完整和准确
      ++(char*)dest;//一个道理,但是注意()的优先级大于++,所以++写在前面
      ++(char*)src;
    }
  }
  else
  {
    //从后向前
    while (count--)
    {
      *((char*)dest + count) = *((char*)src + count);//这个的意思就是为了可以得到我的最后一个数据,因为这个while循环是为了从后向前拷贝,所以一定要先找到dest和src的最后一个数据进行交换
    }
  }
  return ret;
}
#include<stdio.h>
int main()
{
  int i = 0;
  int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
  memmove(arr + 2, arr, 20);//这个就是专门处理内存重叠情况的
  for (i = 0; i < 10; i++)
  {
    printf("%d ",arr[i]);
  }
  return 0;
}
根据上述原理所得的代码,所以这样我就可以通过从后向前,还是从前向后拷贝来很好的解决我的字符覆盖问题,具体讲解全在注释之中

5.所以以上就是关于各种数据拷贝函数的使用和实现

相关文章
|
14天前
|
存储 C语言 开发者
【C语言】字符串操作函数详解
这些字符串操作函数在C语言中提供了强大的功能,帮助开发者有效地处理字符串数据。通过对每个函数的详细讲解、示例代码和表格说明,可以更好地理解如何使用这些函数进行各种字符串操作。如果在实际编程中遇到特定的字符串处理需求,可以参考这些函数和示例,灵活运用。
34 10
|
14天前
|
存储 程序员 C语言
【C语言】文件操作函数详解
C语言提供了一组标准库函数来处理文件操作,这些函数定义在 `<stdio.h>` 头文件中。文件操作包括文件的打开、读写、关闭以及文件属性的查询等。以下是常用文件操作函数的详细讲解,包括函数原型、参数说明、返回值说明、示例代码和表格汇总。
33 9
|
14天前
|
C语言 开发者
【C语言】数学函数详解
在C语言中,数学函数是由标准库 `math.h` 提供的。使用这些函数时,需要包含 `#include <math.h>` 头文件。以下是一些常用的数学函数的详细讲解,包括函数原型、参数说明、返回值说明以及示例代码和表格汇总。
34 6
|
14天前
|
存储 C语言
【C语言】输入/输出函数详解
在C语言中,输入/输出操作是通过标准库函数来实现的。这些函数分为两类:标准输入输出函数和文件输入输出函数。
85 6
|
C语言 图形学 数据格式
C语言库函数大全及应用实例一
原文:C语言库函数大全及应用实例一                                 [编程资料]C语言库函数大全及应用实例一 函数名: abort 功 能: 异常终止一个进程 用 法: void abort(void); 程序例: #i nclude #i nclude .
916 0
|
移动开发 图形学 C语言
C语言库函数大全及应用实例二
原文:C语言库函数大全及应用实例二                                              [编程资料]C语言库函数大全及应用实例二 函数名: bioskey 功 能: 直接使用BIOS服务的键盘接口 用 法: int bioskey(int cmd); 程序例: #i nclude #i nclude #i nclude .
882 0
|
C语言 图形学 数据格式
C语言库函数大全及应用实例一
[编程资料]C语言库函数大全及应用实例一 函数名: abort 功 能: 异常终止一个进程 用 法: void abort(void); 程序例: #i nclude #i nclude int main(void...
782 0
|
移动开发 图形学 C语言
C语言库函数大全及应用实例二
[编程资料]C语言库函数大全及应用实例二 函数名: bioskey 功 能: 直接使用BIOS服务的键盘接口 用 法: int bioskey(int cmd); 程序例: #i nclude #i...
650 0
|
C语言 图形学 数据格式
C语言库函数大全及应用实例三
[编程资料]C语言库函数大全及应用实例三 函数名: ecvt 功 能: 把一个浮点数转换为字符串 用 法: char ecvt(double value, int ndigit, int *decpt, int *sign); 程序例: #i nclude #i nclude #i nc...
666 0
|
图形学 C语言
C语言库函数大全及应用实例四
[编程资料]C语言库函数大全及应用实例四 couble fmod (double x, double y); 返回x对y的模,即x/y的余数。
972 0