【C语言】——内存函数的使用及模拟实现
前言一、
m
e
m
c
p
y
memcpy
memcpy 函数1.1、函数功能(1)函数名理解(2)函数介绍
1.2、函数的使用1.3、函数的模拟实现
二、
m
e
m
m
o
v
e
memmove
memmove 函数2.1、函数功能2.2、函数的使用2.3、函数的模拟实现(1)思路一(2)思路二(3)代码实现
三、
m
e
m
s
e
t
memset
memset 函数3.1、函数功能3.2、函数的使用3.3、函数的模拟实现
四、
m
e
m
c
m
p
memcmp
memcmp 函数4.1、函数功能4.2、函数的使用4.3、函数的模拟实现
前言
在【C语言】——字符串函数的使用与模拟实现(上)与【C语言】——字符串函数的使用与模拟实现(下)二文中,我曾详细介绍了字符串相关函数的具体功能及其模拟实现。 然而,实践过程中,我们往往不仅仅只是处理字符串类型的变量,若要对浮点型、整型、结构体等其他类型的数据进行相关操作,又该怎么办呢? 这时,就需要用到的内存操作函数了 接下来,让我们一起来学习学习吧
一、
m
e
m
c
p
y
memcpy
memcpy 函数
1.1、函数功能
(1)函数名理解
在介绍
m
e
m
c
p
y
memcpy
memcpy函数 的功能前,我们先来对函数名进行分析:
m
e
m
mem
mem 表示的是
m
e
m
o
r
y
memory
memory,它有记忆、内存的意思,这里它表示的是内存
c
p
y
cpy
cpy 即
c
o
p
y
copy
copy,表示拷贝
这样我们从名字就能大体知道这个函数的大致功能啦,像之前的
s
t
r
c
p
y
strcpy
strcpy函数,它是实现对字符串的拷贝,而
m
e
m
c
p
y
memcpy
memcpy函数,实现的是对内存块的拷贝。
(2)函数介绍
功能:将源字符串的前
n
u
m
num
num 个内存块内容拷贝到目标字符串中
函数
m
e
m
c
p
y
memcpy
memcpy 从
s
o
u
r
c
e
source
source 的位置开始向后复制
n
u
m
num
num 个字节的数据到
d
e
s
t
i
n
a
t
i
o
n
destination
destination 指向的内存为止函数的使用需包含头文件
<
s
t
r
i
n
g
.
h
>
s
o
u
r
c
e
source
source 和
d
e
s
t
i
n
a
t
i
o
n
destination
destination 有任何的重叠,复制的结果都是未定义的
d
e
s
t
i
n
a
t
i
o
n
destination
destination 所指向的空间必须足够大,至少
n
u
m
num
num 个字节大小,以避免溢出返回值是目标空间的地址
注:对于重叠的内存,交给
m
e
m
m
o
v
e
memmove
memmove函数 来处理。
1.2、函数的使用
例如:我们将整型数组
a
r
r
1
arr1
arr1 的前 5 个元素复制给
a
r
r
2
arr2
arr2 数组 代码如下:
#include
#include
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[20] = { 0 };
memcpy(arr2, arr1, 20);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr2[i]);
}
printf("\n");
return 0;
}
运行结果: 可能有些小伙伴在调用
m
e
m
c
p
y
memcpy
memcpy 函数时,
n
u
m
num
num 的传参会写成 5 :memcpy(arr2, arr1, 5);,这里需要注意:
m
e
m
c
p
y
memcpy
memcpy 拷贝是内存块为单位,即字节,而不是要拷贝的元素的个数。 当然,
n
u
m
num
num 的传参也可以写成 元素个数
∗
*
∗
s
i
z
e
o
f
sizeof
sizeof(类型) 如:上述代码调用
m
e
m
c
p
y
memcpy
memcpy 函数可写成:memcpy(arr2, arr1, 5*sizeof(int)); 那如果用
m
e
m
c
p
y
memcpy
memcpy 拷贝重叠空间会怎样呢?我们用自己写的模拟函数试一下
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
my_memcpy(arr1 + 2, arr1, 20);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
printf("\n");
return 0;
}
运行结果: 为什么会这样呢?具体原因稍后解释。
1.3、函数的模拟实现
m
e
m
c
p
y
memcpy
memcpy函数 的实现思路和
s
t
r
c
p
y
strcpy
strcpy 的实现差不多,即逐一拷贝每个内存块(详情请看:【C语言】——字符串函数的使用与模拟实现(上))
void* my_memcpy(void* dest, void* src, size_t num)
{
void* ret = dest;
assert(dest && src);
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return ret;
}
代码逐行解析:
参数用
v
o
i
d
void
void* 的原因:
m
e
m
c
p
y
memcpy
memcpy 拷贝的是任意类型的数据,而函数事先并不知道你所用传递的数据的类型,因此用
v
o
i
d
void
void * 指针,以便能接收所有类型的数据。assert(dest && src);断言,后面要对指针进行解引用,因此不能传入空指针。while (num--)循环条件的判断,
n
u
m
num
num 不为 0,即为真,进入循环,后
n
u
m
num
num 自减*(char*)dest = *(char*)src;【C语言】—— 指针一 : 初识指针(上)一文中,曾提到不能对
v
o
i
d
void
void * 指针进行解引用操作,因此要改变其所指向的值,要对其进行强制类型转换。因为
c
h
a
r
char
char * 类型一次的访问权限只有一个字节,因此将其强转成
c
h
a
r
char
char* 类型,再进行解引用、赋值dest = (char*)dest + 1;指针向后移,指向下一个字节
这里有小伙伴可能会问:dest = (char*)dest + 1;我觉得有点啰嗦,能不能写成(char*)dest++;呢? 答案是:不能 为什么呢? 因为强制类型转换是临时的。这里的 ++,不是对
c
h
a
r
char
char* 类型的
d
e
s
t
dest
dest++,而是对
v
o
i
d
void
void* 类型的
d
e
s
t
dest
dest++ 上述代码可拆分成两句:(char*)dest;,dest++; 那((char*)dest)++;可行吗? 基本是没问题的,但需要注意的是,有部分编译器可能编不过去。 因此,循环语句的内容也可写成:
while (num--)
{
*((char*)dest)++ = *((char*)src)++;
}
当然,最好还是上面的那种写法。
二、
m
e
m
m
o
v
e
memmove
memmove 函数
2.1、函数功能
在前面将
m
e
m
c
p
y
memcpy
memcpy函数 时,我曾提到,对于重叠的内存,交给
m
e
m
m
o
v
e
memmove
memmove函数 来处理,那么
m
o
m
m
o
v
e
mommove
mommove函数 又是做什么的呢?
功能:将源字符串的前
n
u
m
num
num 个内存块内容拷贝到目标字符串中
和
m
e
m
c
p
y
memcpy
memcpy函数 的差别就是
m
e
m
m
o
v
e
memmove
memmove函数 处理的源内存块和目标内存块可以重叠的。如果源空间和目标空间出现重叠,就得使用
m
e
m
m
o
v
e
memmove
memmove函数 处理。
2.2、函数的使用
#include
#include
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
memcpy(arr + 2, arr, 20);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
运行结果:
图示:
可以看到,复制 5 个元素,目标空间与源空间重合了,这时,就不能再使用
m
e
m
c
p
y
memcpy
memcpy 函数,而要用
m
e
m
m
o
v
e
memmove
memmove 函数了
2.3、函数的模拟实现
(1)思路一
要实现重叠空间的拷贝,有什么办法呢?
我们想到,可以另外开辟一个数组,将原数组所有数据进行拷贝。然后,再对需要拷贝的元素进行拷贝。
但是,这个方法太过繁琐,而且要单独创建数组,浪费空间,这里我们暂不考虑。
(2)思路二
那还有什么办法呢?我们不妨想一想,为什么内存空间重叠不能用memcpy函数?因为新拷贝的内容会将原来真正想拷贝的内容覆盖,这样最终拷贝出来的结果就不是自己想要的 图示:
那有什么办法可以让先拷贝的内容不覆盖后拷贝的内容呢?
我们可以从后往前拷贝,如图:
这时,有小伙伴就说了:只要以后我们都从后往前拷就行了 但问题又来了,如果是从
a
r
r
arr
arr + 2 开始拷贝 5 个元素到
a
r
r
arr
arr 呢? 如图:
发现又出问题了,但问题和之前一样,先拷贝的内容覆盖了原数据,导致后拷贝的内容错误
这时就需要用到从前往后拷贝了
那什么时候从后往前拷,什么时候从前往后拷呢? 结论:
当两块空间重叠时,目标空间在源空间前面,从前向后拷贝当两块空间重叠时,目标空间在源空间后面,从后向前拷贝当两块空间不重叠时,两种拷贝方法都行
图示:
(3)代码实现
这里,空间不重叠的部分我们用从后向前,因为这样只用在
s
r
c
src
src 处分界即可
void* my_memmove(void* dest, void* src, size_t num)
{
void* ret = dest;
assert(dest && src);
if (src > dest)
{
while (num--)
{
//前->后
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
else
{
//后->前
while (num--)
{
*((char*)dest + num) = *((char*)src + num);
}
}
return 0;
}
这段代码从前往后的情况与
m
e
m
c
p
y
memcpy
memcpy 情况相同,就不再过多解释,来看从后往前的情况
while (num--)这里循环判断条件与上面一样,当
n
u
m
num
num 大于 0,进入循环,随后进行自减。*((char*)dest + num) = *((char*)src + num);这里我们发现,如果要拷贝 20 字节内容,即
n
u
m
num
num 等于 20,那么起始位置加
n
u
m
num
num - 1 所指向的就是最后一个要拷贝的字节,而
n
u
m
num
num 在进入循环后已经自减,因此这里直接加
n
u
m
num
num 。再接着,再次循环,
n
u
m
num
num 再次自减,拷贝倒数第二个字节内容,以此类推,直至
n
u
m
num
num 为 0 拷贝结束。
三、
m
e
m
s
e
t
memset
memset 函数
3.1、函数功能
功能:设置内存,将内存中的值以字节为单位设置成想要的内容
p
t
r
ptr
ptr:指向要设置的内存块
v
a
l
u
e
value
value:要设置的值
n
u
m
num
num:要设置字节的个数返回值:要设置的内存,即
p
t
r
ptr
ptr
3.2、函数的使用
#include
#include
int main()
{
char str[] = "hello world";
memset(str, 'x', 6);
printf(str);
printf("\n);
return 0;
}
运行结果:
这时,有小伙伴问能不能用它来设置整型呢?当然可以
#include
#include
int main()
{
int arr[10];
memset(arr, 0, 10*sizeof(int));
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
运行结果: 那又有小伙伴问全部设置成 1 可以吗?我们来试试
#include
#include
int main()
{
int arr[10];
memset(arr, 1, 10*sizeof(int));
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
运行结果: 为什么会这样? 我们要注意:
m
e
m
s
e
t
memset
memset 是以字节为单位来设置的。 1 的二进制是 00000000 00000000 00000000 00000001 而
m
e
m
s
e
t
memset
memset 是将每个字节单独设置成 1,即一个整型被设置成:00000001 00000001 00000001 00000001,转换成二进制即:16843009
3.3、函数的模拟实现
void* memset(void* ptr, int value, size_t num)
{
assert(ptr);
while (num--)
{
*((char*)ptr + num) = value;
}
return ptr;
}
四、
m
e
m
c
m
p
memcmp
memcmp 函数
4.1、函数功能
功能:比较从
p
t
r
1
ptr1
ptr1 和
p
t
r
2
ptr2
ptr2 指针指向的位置开始,向后的
n
u
m
num
num 个字节 返回值如下:
如果返回值 < 0,则表示 str1 小于 str2。如果返回值 > 0,则表示 str1 大于 str2。如果返回值 = 0,则表示 str1 等于 str2。
4.2、函数的使用
int main()
{
char buffer1[] = "DWgaOtP12df0";
char buffer2[] = "DWGAOTP12DF0";
int n = 0;
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 '%s'.\n", buffer1, buffer2);
return 0;
}
运行结果:
4.3、函数的模拟实现
int my_memcmp(const void* ptr1, const void* ptr2, size_t num)
{
assert(ptr1 && ptr2);
while (*(char*)ptr1 == *(char*)ptr2 && num--)
{
ptr1 = (char*)ptr1 + 1;
ptr2 = (char*)ptr2 + 1;
}
return *(char*)ptr1 - *(char*)ptr2;
}
文章链接
发表评论