【C语言进阶】—— 字符操作函数+内存操作函数详解 (吐血爆肝 !!!)4

简介: 【C语言进阶】—— 字符操作函数+内存操作函数详解 (吐血爆肝 !!!)4

✨七、内存操作函数

1.memcpy

在之前的学习中,我们知道字符串拷贝可以使用strcpy函数,但是,当我们拷贝的数据不是字符串的时候,比如说int类型、double类型,还能使用strcpy函数吗?strcpy函数在拷贝的时候是以\0为字符串拷贝的结束标志,那么在拷贝其它类型数据的时候,拷贝该结束的时候不一定存在\0。所以使用strcpy函数肯定是行不通的。

那怎么办呢?

实际上我们可以使用memcpy函数-- - 内存拷贝函数,用来拷贝任意类型数据。

举例:

#include<stdio.h>
#include<string.h>
int main()
{
    int arr1[] = { 0,1,2,3,4 };
    int arr2[5] = { 0 };
    memcpy(arr2, arr1, sizeof(arr1));
    return 0;
}

【C语言进阶】—— 字符操作函数+内存操作函数详解  (吐血爆肝 !!!)_C语言_33

拷贝前:

【C语言进阶】—— 字符操作函数+内存操作函数详解  (吐血爆肝 !!!)_内存操作函数_34

拷贝后:

【C语言进阶】—— 字符操作函数+内存操作函数详解  (吐血爆肝 !!!)_函数模拟练习_35

算法分析 + 图解:

memcpy函数可以拷贝任意类型的数据,为什么呢?

首先我们思考一下,不同类型的函数参数实际在使用时所对应的内存状态是什么情况?比如说char类型的数据,如果要拷贝的话,是不是应该按照char类型的长度(1)为单位一个字节一个字节来拷贝。int类型的数据,如果要拷贝的话,是不是也应该按照int类型的长度(4)为单位4个字节4个字节来拷贝….如果是double类型呢,8个字节为单位?那结构体呢?

显然,如果我们按照这种方式思考就陷入的经验陷阱中,实际上,如果我们跳出来考虑,无论任何类型的数据,都是1个字节的整数倍(1倍到n倍,n为大于等于1的正整数),如果我们不考虑数据的类型,均按照一个字节一个字节来拷贝,最终不管什么类型的数据都可以成功被拷贝。(这时候,有的同学可能会考虑,既然可以一个字节一个字节来拷贝,为什么不按照1个bit一个bit来拷贝呢?回答:1.没必要,按照字节为单位拷贝已经可以满足我们的需求的,如果要按照更小的单位bit来拷贝只会导致程序执行的次数无端增加。2.操作不方便,以字节为单位拷贝可以通过将指针强制类型转换成char * 来操作,那么以bit为单位呢?该怎么操作,是不是很麻烦!)

【C语言进阶】—— 字符操作函数+内存操作函数详解  (吐血爆肝 !!!)_函数模拟练习_36

函数介绍:memcpy void* memcpy(void* dest, const void* src, size_t count);
头文件:string.h

函数名:memcpy

函数参数:
参数1:destination, 类型:char ,表示内存拷贝的目的位置*
参数2:source,类型:char ,表示内存拷贝的起始位置*
参数3:count,类型:size_t,表示拷贝内存字节的个数

函数返回类型: void, 实际上就是返回destination(目的地)的起始位置*

函数功能:内存拷贝

重点内容:

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

模拟实现函数:

#include<stdio.h>
#include<assert.h>
#include<string.h>

void* my_memcpy(void* dest, const char* src, size_t num)
{   //void* 可接受所有类型指针,但不能解引用操作和运算,那肯定要使用强制类型转换
    void* dest_start = dest; //存储地址

    assert(dest && src);

    while (num--)
    {
        //*(char*)dest = *(char*)src;
        //++(char*)dest
        //++(char*)src

        *((char*)dest)++ = *((char*)src)++;
    }
    return dest_start;
}
int main()
{
    int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int arr2[10] = { 0 };

    my_memcpy(arr2, arr1, 20);//20是字节,强制类型转换为char*步长为1
    return 0;
}

【C语言进阶】—— 字符操作函数+内存操作函数详解  (吐血爆肝 !!!)_字符操作函数_37

注意: mencpy函数应该拷贝不重叠的内存 ,memmove函数可以拷贝重叠的内存

C语言规定menmcy只要实现了不重叠的情况拷贝就可以了,而VS中的memmcy中实现了既可以拷贝不重叠,又可以拷贝重叠。

C语言规定重叠拷贝的情况交给memmove处理就可以了。


2.memmove

假设我们有一个整型数组 1 2 3 4 5 6 7 8 9 10 ,如果我们想要将前5个数字拷贝到第3 - 8个位置上,也就是:

【C语言进阶】—— 字符操作函数+内存操作函数详解  (吐血爆肝 !!!)_函数模拟练习_38

如果我们通过my_memcpy可以做到吗?试验以下就知道了:

执行前:

【C语言进阶】—— 字符操作函数+内存操作函数详解  (吐血爆肝 !!!)_函数_39

执行后:

【C语言进阶】—— 字符操作函数+内存操作函数详解  (吐血爆肝 !!!)_函数模拟练习_40

得到的结果是:1 2 1 2 1 2 1 8 9 10,并不是我们想要的 1 2 1 2 3 4 5 8 9 10

为什么呢?

从我们刚刚模拟实现memcpy中方法中,我们知道memcpy在进行拷贝的时候,是按照从前往后的方式进行的,如果这个地方按照从前往后的方式进行拷贝,那么拷贝一开始的时候,就会出现后面需要被拷贝的数据被覆盖掉,比如说3,一开始将1放到3这个位置,3就被覆盖掉了,后面如果要拷贝3,从这个位置取出数据的时候,实际上取出的是1。
既然从前往后的拷贝方式不行,那么从后往前拷贝呢?

【C语言进阶】—— 字符操作函数+内存操作函数详解  (吐血爆肝 !!!)_函数模拟练习_41

分析后,可以发现从后向前的拷贝方式是可以的,不会出现后面需要被拷贝的数据提前被覆盖的情况。

但是,如果我们拷贝 3 4 5 6 7 到 1 2 3 4 5的位置上,那么从前往后的拷贝方式还行得通吗?
是不是发现从后向前拷贝会出现数据提前被覆盖的情况。所以这时候我们需要进行分情况讨论:

情况一:如果dest在src的右边,如果按照从后往前的方式,就会出现数据提前被覆盖,所以只能按照从前往后的方式拷贝

【C语言进阶】—— 字符操作函数+内存操作函数详解  (吐血爆肝 !!!)_C语言_42

情况二:如果dest在src的左边且dest不超过src + num(拷贝数据的个数,也就是图中紫色方框的横向长度),如果按照从前往后的方式,就会出现数据提前被覆盖,所以只能按照从后往前的方式拷贝

【C语言进阶】—— 字符操作函数+内存操作函数详解  (吐血爆肝 !!!)_内存操作函数_43

情况三:如果dest > src + num(拷贝数据的个数,也就是图中紫色方框的横向长度),那么无论按照从后向前,还是从前向后的方式,均不会出现数据提前被覆盖的情况,所以两个方式均可以。

【C语言进阶】—— 字符操作函数+内存操作函数详解  (吐血爆肝 !!!)_函数_44

为了方便我们实际编程,我们可以将情况二、情况三合并起来,这样就得到:

【C语言进阶】—— 字符操作函数+内存操作函数详解  (吐血爆肝 !!!)_内存操作函数_45

函数介绍:memmove void* memmove(void* dest, const void* src, size_t count);
头文件:string.h
函数名:memmove
函数参数:
参数1:destination, 类型:char* ,表示内存移动的目的位置
参数2:source,类型:char* ,表示内存移动的起始位置
参数3:count,类型:size_t,表示移动内存字节的个数
函数返回类型: void*, 实际上就是返回destination(目的地)的起始位置
函数功能:内存移动

重点内容

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

模拟实现memmove函数

#include<stdio.h>
#include<assert.h>
#include<string.h>

void* my_memmove(void* dest, const void* src, size_t num)
{
    void* dest_start = dest;
    assert(dest && src);
    if (dest < src)
    {
        //从前向后拷贝
        while (num--)
        {
            *(char*)dest = *(char*)src;
            ++(char*)dest; //dest = (char*)dest + 1;
            ++(char*)src;  //src = (char*)src + 1;
        }
    }
    else
    {
        //从后向前拷贝
        while (num--)
        {
            *((char*)dest + num) = *((char*)src + num);//+num字节找到最后的字节就实现从后往前拷贝
        }
    }
    return dest_start;
}
int main()
{
    int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int arr2[10] = { 0 };

    my_memmove(arr1 + 2, arr1, 20);
    return 0;
}

【C语言进阶】—— 字符操作函数+内存操作函数详解  (吐血爆肝 !!!)_C语言_46


3.memcmp(简单了解)

int memcmp(const void* buf1, const void* buf2, size_t count);

(1)比较从ptr1和ptr2指针开始的num个字节
(2)返回值如下 :

【C语言进阶】—— 字符操作函数+内存操作函数详解  (吐血爆肝 !!!)_函数_47

举例:

#include<stdio.h>
#include<string.h>
int main()
{
    int arr1[4] = { 1,2,3,5 };
    int arr2[4] = { 1,2,3,4 };
    int ret = memcmp(arr1, arr2, sizeof(arr1));
    if (ret > 0)
    {
        printf("arr1 > arr2");
    }
    else if (ret == 0)
    {
        printf("arr1 == arr2");
    }
    else
    {
        printf("arr1 < arr2");
    }
    return 0;
}

【C语言进阶】—— 字符操作函数+内存操作函数详解  (吐血爆肝 !!!)_C语言_48


4.memset(简单了解)

用:Sets buffers to a specified character.(将缓冲区设置为指定的字符)
void* memset(void* dest, int c, size_t count);

相关信息:
【C语言进阶】—— 字符操作函数+内存操作函数详解  (吐血爆肝 !!!)_内存操作函数_49

以字节为单位设置内存

举例:

#include<stdio.h>
#include<string.h>
int main()
{
    char arr[] = "abcdefg";
    memset(arr, '*', 4);
    printf("%s", arr);
    return 0;
}

【C语言进阶】—— 字符操作函数+内存操作函数详解  (吐血爆肝 !!!)_函数_50


【C语言进阶】—— 字符操作函数+内存操作函数详解  (吐血爆肝 !!!)_函数模拟练习_51


目录
相关文章
|
1天前
|
编译器
练习使用动态内存相关的4个函数:malloc、calloc、realloc、free
在了解使用动态内存相关的四个函数之前,我们先了解一下,为什么要有动态内存分配?
|
2天前
|
存储 编译器 C++
【C++】内存管理和模板基础(new、delete、类及函数模板)
【C++】内存管理和模板基础(new、delete、类及函数模板)
14 1
|
2天前
|
编译器 C语言 C++
详解内存操作函数
详解内存操作函数
|
7天前
|
缓存 安全 编译器
【C 言专栏】C 语言函数的高效编程技巧
【5月更文挑战第1天】本文探讨了C语言中函数的高效编程技巧,包括函数的定义与作用(如代码复用和提高可读性)、设计原则(单一职责和接口简洁)、参数传递方式(值传递、指针传递和引用传递)、返回值管理、调用约定、嵌套与递归调用,以及函数优化技巧和常见错误避免。掌握这些技巧能提升C语言代码的质量和效率。
【C 言专栏】C 语言函数的高效编程技巧
|
7天前
|
存储 C语言
C语言进阶---------作业复习
C语言进阶---------作业复习
|
7天前
|
存储 Linux C语言
C语言进阶第十一节 --------程序环境和预处理(包含宏的解释)-2
C语言进阶第十一节 --------程序环境和预处理(包含宏的解释)
|
7天前
|
自然语言处理 Linux 编译器
C语言进阶第十一节 --------程序环境和预处理(包含宏的解释)-1
C语言进阶第十一节 --------程序环境和预处理(包含宏的解释)
|
7天前
|
存储 编译器 C语言
C语言进阶第十课 --------文件的操作-1
C语言进阶第十课 --------文件的操作
|
8天前
|
存储 程序员 C语言
C语言进阶第九课 --------动态内存管理-2
C语言进阶第九课 --------动态内存管理
|
8天前
|
编译器 C语言
C语言进阶第九课 --------动态内存管理-1
C语言进阶第九课 --------动态内存管理