上篇文章介绍了C语言字符串函数,我们学会了一些对字符串的操作,比如说将字符串从一个字符数组拷贝到另一个字符数组中,我们可以通过使用strcpy函数实现。但是,如果我们想要拷贝一个整型数组到另一个整型数组中时,strcpy函数就失效了,那我们应该怎么才能实现这个操作呢?不要着急,本篇文章将带大家搞定这个问题。
cplusplus上的介绍:
作用介绍:
参数介绍:
返回值介绍:
(1)void* destination
该参数的作用是目标空间,用来存放将要拷贝的内存,为什么返回值是 void* 呢?这是因为这个函数的作用是内存拷贝,既然是内存拷贝,内存中又可能存放的是整型数组,也有可能存放的是字符数组……,因此我们不关心存放数据的类型,因此使用void*指针来接收任意类型的数据的地址。
(2)const void* source
source是源头,也就是要拷贝的内存数据,这里也是void*指针是因为我们不知道我们未来要拷贝的数据是什么类型的,可能是整型,可能是字符,也可能是结构体,因此我们也使用void*。
用const修饰是因为我们不希望要拷贝的数据被修改,因此使用const修饰会使得整个工程更加稳定。
(3)size_t num
num的作用是限定拷贝的字节数,比如说source中有十个字节的数据,我们可以通过修改num的值来拷贝我们想要的个数,num为5,我们就拷贝五个字节的数据到destination中;num的类型是size_t的原因是我们拷贝的个数最低都是0个,不会出现负数的情况,因此使用size_t类型最为合适。
前面学习了memcpy函数,接下来我们将使用memcpy函数来实现一些操作
比如说我们打算将整型数组arr1[ ] = {1,2,3,4,5,6,7,8,9,10}拷贝到整型数组 arr2[10] = { 0 };
#include <stdio.h>
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
memcpy(arr2, arr1, 5 * sizeof(int));
return 0;
}
调试结果:
源头从 3 开始拷贝,比如说:
#include <stdio.h>
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
memcpy(arr2, arr1+2, 5 * sizeof(int));
return 0;
}
一些总结:
接下来我们尝试自己写一个函数来实现memcpy的功能
void* my_memcpy(void* dest, const void* src, size_t num)
{
}
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
my_memcpy(arr2, arr1, 5 * sizeof(int));
return 0;
}
注意:在my_memcpy中,源头是void*指针类型的
现在有一个问题,我们不知道我们要拷贝的是内容是什么类型的,我们只知道要拷贝的是20个字节,我们该怎么将这20个字节拿到arr2中呢?
在前面学习qsort函数的模拟实现中,我们用到了一个方法,我们可以一个字节一个字节的拷贝,那么就可以使用强制类型转换将void*指针转换为char*指针
void* my_memcpy(void* dest, const void* src, size_t num)
{
while (num--)//num是字节总数,因此num每减1,就拷贝一个字节
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
调试结果:
总代码:
#include <stdio.h>
void* my_memcpy(void* dest, const void* src, size_t num)
{
// 保存目标地址,以便最后返回
void* ret = dest;
// 循环 num 次进行逐个字节的复制
while (num--)
{
// 将源地址指向的内容复制到目标地址指向的位置,并转换为 char* 类型进行操作,确保每次只复制一个字节
*(char*)dest = *(char*)src;
// 目标地址向后移动一个字节
dest = (char*)dest + 1;
// 源地址向后移动一个字节
src = (char*)src + 1;
}
// 返回复制后的目标地址
return ret;
}
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
my_memcpy(arr2, arr1, 5 * sizeof(int));
return 0;
}
如果我们向将int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };中(1,2,3,4,5)拷贝放到(3,4,5,6,7)的位置上,这样可以实现吗?
my_memcpy(arr1+2, arr1, 5 * sizeof(int));
调试结果:
为什么这里会是(1,2,1,2,1)呢?其实也很好理解
因此最终结果是(1,2,1,2,1,2,1,8,9,10)
关于重叠问题,我们一般使用后面的这个函数memmove函数
而memcpy函数一般用来处理不重叠情况。
在vs2022中,memcpy的能力是比较强的,也是可以用来处理重叠问题,但是对于memcpy函数,本来的作用是不包括处理重叠的问题的,这就像是老师说让你考到60分就行,但是你能考100分。但是不能保证在所有的编译器上memcpy都可以考到100分
也就是说无法确定其它编程环境是否可以实现,因此,如果要处理重叠问题,最好还是交给memmove函数.
memove函数的使用与memcpy函数是一样的,也是用来实现内存中数据的拷贝的,因此就不详细介绍了。不过前面也说了memmove函数可以实现重叠拷贝,来测试一下
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
memmove(arr1+2, arr1, 5 * sizeof(int));
return 0;
}
调试结果:
可以看到的确将(1,2,3,4,5)的内容拷贝到(3,4,5,6,7)的位置上了
那么memmove函数究竟是怎么实现这个操作的呢?我们来模拟了解一下
前面我们知道,如果拷贝1,会把3给覆盖,拷贝2,会把4给覆盖。
该怎么拷贝才能实现不被覆盖呢?
可以从后向前拷贝,先拷贝5,覆盖7,在拷贝4覆盖6,这时候在拷贝3覆盖5,拷贝2覆盖4,拷贝1覆盖3,由于3,4,5已经拷贝完成,不会出现还没有拷贝就被覆盖的情况。
那是不是从后向前拷贝就一定正确呢?
我们在换一种情况试试:
这时候如果还是从后向前拷贝的话会出现什么问题呢?
8拷贝到6,7拷贝到5,这时候向拷贝6的时候已经变成了8,因此从后向前失效了。
这时候我们在从前向后拷贝,3拷贝到1,4拷贝到2……恰好可以全部拷贝。
不知道大家有没有发现一个规律: 如果dest在src的后面,则从后向前拷贝; 如果dest在src的前面,则从前向后拷贝; 如果没有重叠,则随意。
如果是 后->前,该怎么拷贝呢?
比如说先拷贝5,我们只需要在起始位置跳过num个字节即可
比如说:*((char*)src + num)
代码实现:
#include <stdio.h>
#include <string.h>
void* my_memmove(void* dest, const void* src, size_t num)
{
void* ret = dest;
if (dest < src)
{
//前->后
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
else
{
while (num--)
{
//后->前
//num进来减1,变为19,src加上19后跳到最后一位上,也就是5,dest加上19跳到8的位置,然后将5赋值到8的位置
*((char*)dest + num) = *((char*)src + num);
}
}
return ret;
}
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
my_memmove(arr+2, arr, 5 * sizeof(int));
return 0;
}
参数介绍:
注意:
memset是以字节为单位来设置内存的 ,而不是以一个元素为单位设置的。
作用介绍:
返回值介绍:
那么memset函数究竟有什么作用呢?
比如说:
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int i = 0;
for (i = 0; i < 10; i++)
{
arr[i] = i + 1;
}
return 0;
}
我们想将arr数组全部初始化为0,我们该怎么做呢?
你可能会说这不简单?直接使用循环不就可以了吗?
for (i = 0; i < 10; i++)
{
arr[i] = 0;
}
这样的确可以,不过我们也可以使用库函数memset函数来实现这个操作。
我们要设置的这个空间整型数组arr[10]的地址交给ptr,而数组的地址就是数组名arr,我们需要将该数组的元素都变为0,也就是要设置的值value为0,由于是整型数组,有十个元素,所以num就等于40字节。
代码展示:
#include <stdio.h>
#include <string.h>
int main()
{
int arr[10] = { 0 };
int i = 0;
for (i = 0; i < 10; i++)
{
arr[i] = i + 1;
}
/*for (i = 0; i < 10; i++)
{
arr[i] = 0;
}*/
memset(arr, 0, 10 * sizeof(int));
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
调试过程:
结果展示:
如果memset(arr, 1, 10 * sizeof(int));这是否是将每一个元素都改为1了呢?
调试监控窗口
为什么没有达到想要的结果呢?
我们在来看一下内存窗口:
破案了,memset函数将每一个字节都设置为1,而不是把一个元素设置为1。
前面强调了memset是以字节为单位来设置内存的 ,而不是以一个元素为单位设置的。
代码:
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = "hello world";
//如何将helo改为五个x
memset(arr, 'x', 5);
return 0;
}
调试结果:
如果我们想修改world呢?
memset(arr+6, 'x', 5);
从前向后数hello五个字符,还有一个空格,共6个字符。
注意:这个函数比较常见,因此需要熟练掌握!!!
memcmp函数与之前学习的strcmp函数的功能是比较相似的,不过strcmp函数只能用来做字符串的比较,而memcmp函数是用来做内存块的比较,不论是什么类型。
参数介绍:
返回值介绍:
直接上例题,比较arr1与arr2中前3个元素
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[] = { 1,2,3,3 };
int ret = memcmp(arr1, arr2, 12);
printf("%d\n", ret);
return 0;
}
结果:
如果我们比较前4个元素呢?
int ret = memcmp(arr1, arr2, 16);、
这里返回的就是1了。
结语:本篇文章到这里就结束啦!期待下次的相遇!!