在上一篇博客我们了解了字符函数和字符串相关的函数,都是用来对字符串进行各种常用操作的函数,如果我们想要将这些函数扩大作用范围,只靠这些函数是远远不够的,因为这些函数是专门针对字符串的。那C语言中有没有函数能针对所有数据类型进行类似字符串函数的函数呢?答案是肯定有的,接下来我将以这篇博客一一向大家介绍。
目录
正文开始
一、memcpy()函数的使用和模拟实现
1.memcpy()函数的使用
memcpy()函数的作用是用来从源内存区域复制数据到目标内存区域,对应字符串函数中的strcpy()函数,但是它适用于所有的数据类型,复制方法依然是逐字节复制。要想使用它需要包含string.h头文件。它的函数原型如下:
void* memcpy(void* destination, const void* source, size_t num);
其中,destination是指向目标内存块的指针;source是指向源内存块的指针;num是要复制的字节数。使用memcpy()函数需要注意:
函数memcpy从source的起始位置开始向后复制num个字节的数据到destination指向的内存位置
这个函数在遇到 '\0' 的时候并不会停下来
如果source和destination有任何的重叠,复制的结果都是未定义的
cpp
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
memcpy(arr2, arr1, 20);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr2[i]);
}
return 0;
}
memcpy()函数不会检查源内存块中是否存在终止字符,它只会简单地复制指定数量的字节。所以如果源内存块和目标内存块有重叠,使用memcpy()函数可能导致未定义行为,因为它不会保护已经复制的数据不被覆盖:
在这种情况下,应该使用memmove()函数,它可以安全地处理内存重叠的情况。
2.memcpy()函数的模拟实现
在C语言:指针详解(4)-CSDN博客中实现升级版的冒泡排序时,考虑到升级版冒泡排序的万能性,我们需要考虑到所有的数据类型,所以我们在交换元素的位置时,是先将指向元素的指针强转为char*指针后,再通过移动char*指针来逐个交换字节顺序从而实现元素的交换。在模拟实现memcpy()函数的时候也是如此,memcpy()函数也是具有万能性的,适用所有的数据类型,所以我们在进行数据拷贝的时候也需要先将指向元素的指针强转为char*指针,再通过逐个拷贝字节的方式实现数据的拷贝。
由于我们要拷贝num个字节,所以我们可以定义一个循环以num作为条件来实现逐个拷贝字节。首先我们需要创建一个指针来存储指向目标空间首元素地址的指针,随后再通过一个循环来实现字节的拷贝,但是在拷贝的过程中需要注意我们是要实现字节的拷贝,所以要将指向元素的指针强转为**char***指针。当我们完成了字节的拷贝后,我们便可以直接返回一开始创建的存储目标空间首元素地址的指针。依据此思路,我们可以写出以下的代码:
cpp
void* memcpy(void* dst, const void* src, size_t count)
{
void* ret = dst;
assert(dst);
assert(src);
while (count--)
{
*(char*)dst = *(char*)src;
dst = (char*)dst + 1;
src = (char*)src + 1;
}
return ret;
}
详细思路与实现升级版的冒泡排序大致相同,即将字节视为一个个小元素并进行赋值拷贝,然后再将指针向后移动一格。这里不再过多赘述~
二、memmove()函数的使用和模拟实现
由于memcpy()函数只会进行简单的数据拷贝,所以它并不会保护原来目标空间内部的数据,使用时可能会导致数据的丢失,但是它也不是一无是处:当需要复制大型数据结构或数组时使用memcpy()函数可以提高效率;在处理二进制数据时,memcpy()函数可以确保数据的完整复制,包括那些可能包含空字符的数据。如果我们不希望让原目标空间内部的数据丢失,这时我们需要用到比memcpy()函数使用更加广泛的函数------memmove()函数。
1.memmove()函数的使用
memmove()函数的作用其实和memcpy()函数的作用是一样的,但是唯一不同的一点也是区别两者的一点就是memmove()函数可以处理内存重叠的情况,它可以确保数据在复制过程中不会丢失。这是通过在复制时使用临时变量或从后向前复制的方式来实现的。如果源内存和目标内存不重叠,memove()函数与memcpy()函数相同,会直接进行拷贝。它的原型如下:
void* memmove(void* destination, const void* source, size_t num);
cpp
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
memmove(arr1 + 2, arr1, 20);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
2.memmove()函数的模拟实现
与memcpy()函数的模拟实现类似,但是不同点就在于memmove()函数需要考虑到内存重叠的情况。所以只需要判断有无内存重叠即可。当源地址和目标地址完全不存在内存重叠或者源地址在目标地址之后,我们需要采用正向拷贝,如何理解这句话,我们来画图理解:
这里有两个数组分别是arr1和arr2,绿线后面的部分代表的就是两个数组内存重叠的部分。现在我们要将arr2中的内容拷贝到arr1就要采用正向拷贝,即从两个数组的首元素开始向后拷贝。当我们拷贝前三个元素的时候,一切都是正常的:
当dst来到arr1和arr2的内存重叠部分时,这时的拷贝就变得不一样了。当我们将arr2的第四个元素拷贝到arr1时,由于arr1的第四个元素和arr2的第一个元素的内存重叠了,改变arr1的同时也会改变arr2,所以:
而原来arr2的第一个元素就会被覆盖,由原来的1变为4。后面也是同理。将arr2的5拷贝到arr1的第五个元素,arr2的第二个元素和arr1的第五个元素的地址是相同的,因此arr2的第二个元素也会被改为5。这也就是为什么要正向拷贝的原因。如果是采用反向拷贝的话,可能就会导致内存的覆盖,出现有部分数据没有成功地拷贝上。
当然,如果arr1和arr2完全没有内存重叠的地方,这时memove()函数的作用就和memcpy()函数的作用是一样的了。
再来考虑第二种情况,即源地址在目标地址之前,且有内存重叠。其实也是很好想的,我们只需把上面的图中的arr1和arr2调换一下方向就是该种情况:
其中arr2为源数组,arr1为目标数组,绿线后面的部分为内存重叠部分。我们已经将前4个元素从arr2中正向拷贝到了arr1当中,由于arr1前4个元素已经和arr2有了内存重叠的部分,所以arr2对应的位置也会发生数值的改变:
当我们试图将arr2中的5,6,7,8拷贝到arr1当中,却发现arr2中早就没有了这四个元素,在拷贝当中就已经丢失。而我们将这四个内存重叠的位置的元素拷贝到arr1当中就会发现依然是1,2,3,4这四个元素。这里在编程的结果以及调试会更加明显地呈现出来,这里就不再作过多调试,请各位自行编写对应的代码进行调试观察以便更好地了解!
所以在这种情况下,由于arr1后面有一部分不是内存重叠的位置,所以我们要采用反向拷贝的方法来进行拷贝,从arr2的最后一个元素的地址向前拷贝。这样即便造成了数据的覆盖,也不会造成拷贝数据的错误。
总结和理清一下上面的思路,我们就可以得出下面的代码:
cpp
void* my_memmove(void* dst, const void* src, size_t count)
{
void* ret = dst;
if(dst <= src || (char*)dst >= ((char*)src + count))
{
while(count--)
{
*(char*)dst = *(char*)src;
dst = (char*)dst + 1;
src = (char*)src + 1;
}
}
else
{
dst = (char*)dst + count - 1;
src = (char *)src + count - 1;
while(count--)
{
*(char*)dst = *(char *)src;
dst = (char*)dst - 1;
src = (char*)src - 1;
}
}
return ret;
}
memmove()函数在大多数情况下比memcpy()函数更加好用,它通常用于需要安全移动内存数据的场景,尤其是当不确定源和目标内存是否重叠时。例如,在动态内存管理中调整数组大小或重新排序数据时,memmove()函数可以防止数据损坏。此外,memmove()函数也常用于实现某些数据结构的维护操作,如链表节点的插入和删除。这在后期进入数据结构的学习时会更加明显地体现出来。
三、memset()函数的使用
memset()函数是用于填充内存区域的函数。它的原型如下:
void* memset(void* ptr, int value, size_t num);
它可以用来将内存中的值以字节为单位设置成想要的内容:
cpp
#include <stdio.h>
#include <string.h>
int main()
{
char str[] = "hello world";
memset(str, 'x', 6);
printf("%s\n", str);
return 0;
}
在使用memset()函数时需要注意:
传递给memset()函数的值应当是一个无符号字符,因为内存是按字节填充的。
如果ptr指向的内存区域与其他内存区域重叠,使用memset()函数可能会导致未定义行为。在这种情况下,应该使用memmove()函数。
memset()函数不会考虑内存对齐问题,因此即使是对齐的内存区域也可以安全使用memset()函数。
memset()函数的模拟实现请各位自行实现~
四、memcmp()函数的使用
memcmp()函数用于比较两块内存区域的内容。它按照字节顺序比较,直到遇到第一个不同的字节或者达到指定的字节数为止。如果两块内存完全相同,返回0;如果第一块内存小于第二块,返回负数;如果第一块内存大于第二块,返回正数。它的原型如下:
int memcmp(const void* ptr1, const void* ptr2, size_t num);
cpp
#include <stdio.h>
#include <string.h>
int main()
{
char buffer1[] = "DWgaOtP12df0";
char buffer2[] = "DWGAOTP12DF0";
int n;
n = memcmp(buffer1, buffer2, sizeof(buffer1));
if (n > 0)
printf("'%s' is greater than '%s'.\n", buffer1, buffer2);
else if (n < 0)
printf("'%s' is less than '%s'.\n", buffer1, buffer2);
else
printf("'%s' is the same as '%s'.\n", buffer1, buffer2);
return 0;
}
memcmp()函数的模拟实现请各位模拟实现~
完