说实话有没有兄弟是真的看完了上一篇博客的?真的是万字血书啊!我真的感觉我的键盘都快冒烟了,手指头都小了一圈呢!
其实本来是我想着把字符串函数还有内存函数合在一起搞一个大章节的,但是上一篇博客敲到最后实在的没有精力了,所以我们迂回一波,现在记好笔记,我们来上课!
1.memcpy
在上一篇博客里面,我们在前期就详细讲解过一个strcpy函数,当时我还给大家特别强调了,在上一节课里面我们所讲的都是字符串函数,都是只能对字符串进行操作的,但是我们在敲代码的时候不可能只使用字符串啊,所以当我们遇到了别的类型时,相应的我们也要做出改变,这种时候,memcpy函数就闪亮登场了,这里我们讲解的函数都是内存函数,听着是不是就比字符串函数好像高级一点?
事实上内存函数确实不光可以做到字符串函数可以做到的事情,他还可以做到许许多多和内存相关的事宜!
好了我们先来看函数吧:
memcpy 函数的参数增加到了三个,第一个还是目的地地址,第二个参数是来源地地址,而增加的那个是size_t num 我们之前就给大家演示过这个size_t num 在内存中其实就是一个无符号整数,而增加的变量其实就是以字节为单位的要拷贝的内存大小!所以我们对于内存的拷贝拥有了更大的权利,我们已经可以决定拷贝多少字节了!
另外还值得我们拎出来单讲的可能就是这里的void 类型了!
兄弟们现在估计也是一脑袋问号,我i知道你很急,但是请你先不要急,我来给你细细道来:之前的 strcpy函数中我们之所以会使用char类型,就是因为我们心里门清我们要操作的对象就是字符,所以才敢用char*,但是同志们,同志们,那要是我们这次想要拷贝的内容是int类型的?是float类型的?那你还是只会老实巴交地使用char* ,这不合适吧……
我们之前还是讲过void* 是一个兼容性尤其强的类型,无论什么类型都是可以被放进这个类型的,所以这个时候我们很灵性的使用 void* ,我相信就算是计算机之父,都会在某个地方微笑着给你鼓鼓掌!包括我们这里返回类型的void* 这都是一个道理!好了说了这么多,不如直接上手:
void* mymemcpy(void* a, void* b, size_t num)
{
void* str = a;//上一篇博客我们也是采用了类似的方式,记录起始的位置
//方便我们给它传回去
while (num--)//strcpy是根据‘\0’来判断是否结束的,
//而现在我们需要通过字节数目
{
*(char*)a = *(char*)b;//void*的兼容性强,只是代表可是放得下所有类型
//但是使用的时候还是需要具体的类型的,不然你怎么知道步长是多少?你怎么知道
//访问了多少内存?又由于我们是按照一个单位字节大小进行操作的,那么char*类型
//就刚刚好满足了我们的需求,一次拷贝一个字节,完美契合!!!
a = (char*)a + 1;//还是那句话,不知道变量类型可不敢对变量进行随意的操作
//所以强制类型转化之后才敢进行偏移
b = (char*)b + 1;
}
return str;//返回我们目的地的起始地址
}
int main()
{
int a[15] = {0};
int b[10] = {0};
for (int i = 0;i < 10;i++)//这里在巩固一下基本功,我觉得手动写入各个值
//有一点不够雅!这回我们洋气一点!
b[i] = i;
int* c =(int *)mymemcpy(a, b, 5 * sizeof(int));//注意看这第三个参数
//我并没有直接写出我要拷贝的是多少个字节,而是通过这样一种sizeof方式
//得到了我想要的效果,这就省得我进行计算了
//再者就是这边的函数返回的是一个void*类型的,而我们身为程序员,是知道我们
//想要的类型是什么的,因此我们可以直接强制类型转化,并且创建一个新变量来接受一下
for (int i = 0;i < 15;i++)
{
printf("%d ",c[i]);//这里我们是把c想象成了c【】反正我们知道c的类型是int*
//那么它解引用后一个变量的大小不就是一个int嘛?
}
return 0;
}
2.memmove
这里冒出一个相对而言比较新的伙伴:memmove ,这个函数和上边我们讲的 memcpy是不是无论从面子还是里子上面都是高度相似的?
事实上确实如此,memmove 的应用环境大致就是自己给自己拷贝,也就是利用自己内存中的部分内容把另外一段内容给它覆盖掉了,如果是我们上面的那个函数似乎并不足以支持我们进行如此操作吧!
举个例子吧,就比如说我们想要用蓝色区域的那五个数字覆盖前面五个数字,最终达到:
下面的效果我们应该怎么办?是不是把五给一然后依次往后面推?
那如果我们想要把这里的蓝色区域覆盖到最后的五个数字呢?是不是还是荣前面往后面推呢?
显然不是啊,从前面往后面赋值的话就会改变我们需要用来覆盖的内容,所以我们就需要从后面开始往前赋值。
其实说着像是比较复杂,但是其中的逻辑关系并不难啊,我们只要保证在赋值的过程中,我们还未给别人赋值的对象不改变即可!
其实这就是一个简简单单的位置学,只要我们的目的地起始地址在源头地的前面我们就采用从头开始赋值的方法,其余的统统采用从后向前的就可以了;这里要说明一下的就是只有当我们目的地和源头地整体有重叠地方的时候才需要,谁在谁的前面,如果没有交集,那么无论怎么赋值其实都是无所谓的!
void* mymemmove(void* des, void* sou, size_t num)
{
void* p = des;
if (des > sou)
{
while (num--)
{
*(((char*)des)+num-1) = *(((char*)sou) + num-1);
//这边比较复杂的也许也就是这个括号太多了,但是没办法啊
//这个括号里面的优先级乱七八糟的,不加这个括号顺序不对,那程序
//怎么可能对呢?
//一般这种情况我们可以选择智取,就是把一个很复杂的式子拆解成
//几个简单的式子
//dst = (char*)dst + count - 1;
//src = (char*)src + count - 1;
//while (count--) {
// *(char*)dst = *(char*)src;
// dst = (char*)dst - 1;
// src = (char*)src - 1;
//这样子就避免了我们为了进行类型转换和偏移所做的的巨大努力
}
}
else
{
while (num--)
{
*(char*)des = *(char*)sou;
des = (char*)des+1;
sou = (char*)sou + 1;
}
}
return p;
}
int main()
{
int a[10] = {0};
for (int i = 0;i < 10;i++)
{
a[i] = i + 1;
}
int* b = (int*)mymemmove(a + 1, a + 5, 3 * sizeof(int));
for (int i = 0;i < 9;i++)
{
printf("%d ",b[i]);
}
return 0;
}
可以看到我们很出色地完成了任务!
顺带说一下吧,在我们有些编译器里面memcpy也是可以完成memmove的工作的,这就对于memmove点尴尬了,其实这波啊是编译器负全责,它太强大了,强大到可以优化我们的库函数,等于是做出了附加题,这谁顶得住?
但是我们作为程序员怎么可以过度依赖编译器呢?万一你的公司给你提供的编译器是石器时期的,你怎么办?所以我们要知道它的实现原理,那么无论什么编译器,只要能看得懂我敲的代码,都是可以自己实现的!
好了,那么代码有难度,但是我们细细想来还是可以解决的,希望我的这篇博客对于大家有帮助!
百年大道,你我共勉!!