手把手教你用C写游程编码
来源:互联网 发布:eclipse加载jar包源码 编辑:程序博客网 时间:2024/05/15 20:36
手把手教你用C写游程编码
(原创作品,作者Shawn, 转载请声明)
相信大家对游程编码的概念不会陌生。如果用C语言亲自实现一遍游程编码的话,会发现综合运用到了很多C语言的知识和要避免踩到坑!
游程编码:
给定一串数据,如: 0x11,0x22,0x11,0x11,0x11,0x34,0x34,0x22
编码后,得到: 0x11,0x01,0x22,0x01,0x11,0x03,0x34,0x02,0x22,0x01
废话少说,立马手把手教你用C写游程编码!这里用到函数分层的思想,即模块化处理:
输入的数据–>用函数处理–>返回输出数据。
即数据的处理完全由函数内部解决。
敲黑板划重点!!!
C语言就是玩转内存空间!
C语言就是玩转内存空间!!
C语言就是玩转内存空间!!!
因此要时刻谨记和扪心自问三个哲学问题:
1. 要操作的对象是什么?(大小,即数据类型)
2. 分配的内存空间在哪里?(内存的哪个段)
3. 处理后的内存空间要去哪里?(注意指针的坑)
下面展开来说。
要操作的对象是什么?
- 根据对象的需要,分配相应的数据类型。数据类型可以分为两种:
- 内置数据类型:比如,int, float, double.
- 自定义数据类型:比如,结构体, 数组(本质是内置数据类型的组合)。结构体适用于不同内置数据类型的组合,例如应用在通信协议的设计;数组适用于也必须用于相同数据类型的组合。
先看原始数据:
原始数据是1字节的序列,即大小为1字节的(char)、连续的内存空间。可见,输入数据可使用数组表示:
char raw_data[]={0x11,0x22,0x11,0x11,0x11,0x34,0x34,0x22};
如果你愿意更严谨一些,可以表示为
unsigned char raw_data[]={0x11,0x22,0x11,0x11,0x11,0x34,0x34,0x22};
以此来向别人表明这些数据是不考虑正负号的。
但其实两种写法都可以,原则上都ok(因为编译器才是老大,它说ok就妥妥的)。
此处为了方便,采用char raw_data[]
的写法。
原始数据的内存空间应该分配在哪里呢?
这引出了新的问题,分配的内存在哪几个段(segment)?
具体如下:
* 也存在只读变量,此处不展开说明。
此处,我们简单粗暴地把原始数据空间定义在main函数里,也就是说,是在栈空间,因为定义在main函数内部,且没有static修饰符,所以既不是全局变量、也不是静态变量。
详情可参考该链接
再看结果数据:
此处我要用一个名为runLenEncoding
的函数(run-length encoding游程编码)来处理输入数据。
那么现在问题来了,在真正调用该函数,传入raw_data数据之前,该函数根本不知道需要为结果数据分配多少内存空间(因为这里假定所有数据处理都在该函数内部,所以不能在进入该函数前就处理数据,从而无法预先得知结果数据空间的大小!)。
好纠结,纠结不如敲几行代码:
#include<stdio.h>void runLenEncoding(void);int main(){ //原始数据空间,这里简单粗暴地放在栈空间即可 char raw_data[]={0x11,0x22,0x11,0x11,0x11,0x34,0x34,0x22}; //结果数据空间。应该是啥? //执行函数,进行游程编码,返回什么给结果数据空间? result_data=runLengEncoding(); return 0;}
结果数据的内存空间应该分配在哪里呢?
考虑到在runLenEncoding
函数中,经过一定数据处理,才能得知结果空间的大小。也就是说,不可能是静态内存分配,函数还没处理,我怎么知道需要为结果数据分配多大空间?
剩下两个动态内存分配:
1. 栈。还是不靠谱:
- 函数一结束返回,则栈空间的数据就销毁,返回的地址就没有意义了——因为此时该地址的数据已经被系统回收了,不存在了。
- 更何况,类似静态内存分配那样,在编写代码时就需要确定空间的大小,因而同样无法满足函数需要动态增加内存的需求。
2. 堆。妥妥的。堆具有两个优势:
- 堆空间的生命周期由程序员说了算,由程序员编写代码创造和毁灭。也就是说,通过malloc()
函数创造的堆空间,在用了free()
函数后,该空间才会被销毁。因此,不存在栈空间的缺点:在函数返回后,栈空间则销毁(数据/资源被系统回收)。
- 堆空间可以不是在编写程序时就规定好的。也就是,可以根据程序实际需要,真正地实现按需分配空间大小。
因此,在runLenEncoding
函数内部创建堆空间,即结果数据空间,返回该数据空间的首地址。
处理后的内存要去哪里?
main
函数用一个result_data
指针接受该地址。
妥,继续敲几行。
#include<stdio.h>char* runLenEncoding(const char*);int main(){ //原始数据空间,这里简单粗暴地放在栈空间即可 char raw_data[]={0x11,0x22,0x11,0x11,0x11,0x34,0x34,0x22}; //结果数据空间(实质是指向结果数据空间的指针)。初始化指向NULL char *result_data=NULL; //调用函数,进行游程编码,返回堆空间(结果数据空间)的首地址给main函数中的结果指针? result_data=runLenEncoding(raw_data); //返回指针后,对指针进行检查 if(result_data==NULL){ printf("Get result_data failed!\n"); return -1; } return 0;}
打印,检查输入和输出
仔细想想,输出的数据我要打印出来检查一下对不对呀。要操作的对象是数组,脑海里立马是for
循环。既然要打印输出数据,那把输入数据也打印吧,正好对比着看,那就写一个check_data
函数,两个都可以调用(模块化设计哦!)。
用到了for
循环,那条件中的结束标志当然是数组中的数据个数
,因此要分别定义两个变量raw_data
和result_data
。
raw_data
可以简单粗暴地求出来:
raw_num=sizeof(raw_data)/sizeof(raw_data[0]);
反向更新
但result_data
不可以事先确定(因为正如前文所说,假定数据处理都放在runLenEncoding
函数中)。所以,只能让runLenEncoding
子函数帮帮忙,即为反向更新。
将result_data
的首地址作为参数传入runLenEncoding
函数参数列表中。
runLenEncoidng(raw_data, &result_num);
就像是main
函数拜托runLenEncoding
函数:
“儿子啊,爹靠你了。result_data的地址给你了,靠你帮爹处理好,爹不管了,你开心就好。”
代码更新如下:
#include<stdio.h>char* runLenEncoding(const char*, const int*);void check_data(const char*, int);int main(){ //原始数据空间,这里简单粗暴地放在栈空间即可 char raw_data[]={0x11,0x22,0x11,0x11,0x11,0x34,0x34,0x22}; //结果数据空间(实质是指向结果数据空间的指针)。初始化指向NULL char *result_data=NULL; //原始数据的个数 int raw_num=0; //结果数据的个数 int result_num=0; //求出原始数据的个数 raw_num=sizeof(raw_data)/sizeof(raw_data[0]); printf("raw_num is %d\n",raw_num); printf("raw_data is \n"); //打印原始数据 check_data(raw_data, raw_num); //调用函数,进行游程编码,返回堆空间(结果数据空间)的首地址给main函数中的结果指针? result_data=runLenEncoding(raw_data, &raw_num); //返回指针后,对指针进行检查 if(result_data==NULL){ printf("Get result_data failed!\n"); return -1; } //打印结果数据 printf("result_data is \n"); check_data(result_data, result_num); return 0;}void check_data(const char* data, int num){ for(int i=0;i<num;i++){ printf("0x%02x\t",data[i]); } printf("\nCheck_data complete!\n");}
防止指针越界
仔细想想,传入的是原始数据raw_data
的首地址,runLenEncoding
函数在函数内部用result
指针接收后,如果不知道原始数据空间的长度,胡乱地指向,越界就可怕了——访问并修改了其他变量数据,那还有王法吗?!
于是,应该为runLenEncoding
函数的参数列表添加raw_num
,以此告诉子函数raw_data
的长度。
runLenEncoding(raw_data, raw_num, &result_num);
好,我改改改。
#include<stdio.h>char* runLenEncoding(const char*, int,int*);void check_data(const char*, int);int main(){ //原始数据空间,这里简单粗暴地放在栈空间即可 char raw_data[]={0x11,0x22,0x11,0x11,0x11,0x34,0x34,0x22}; //结果数据空间(实质是指向结果数据空间的指针)。初始化指向NULL char *result_data=NULL; //原始数据的个数 int raw_num=0; //结果数据的个数 int result_num=0; //求出原始数据的个数 raw_num=sizeof(raw_data)/sizeof(raw_data[0]); printf("raw_num is %d\n",raw_num); //打印原始数据 printf("raw_data is \n"); check_data(raw_data, raw_num); //调用函数,进行游程编码,返回堆空间(结果数据空间)的首地址给main函数中的结果指针? result_data=runLenEncoding(raw_data, raw_num, &result_num); //返回指针后,对指针进行检查 if(result_data==NULL){ printf("Get result_data failed!\n"); return -1; } //打印结果数据 printf("result_data is \n"); check_data(result_data, result_num); return 0;}char *runLenEncoding(const char* raw_data, int raw_num, int* result_num){//用以指向结果数据空间的指针 char* result=NULL;//返回结果数据空间的首地址 return result;}void check_data(const char* data, int num){ for(int i=0;i<num;i++){ printf("0x%02x\t",data[i]); } printf("\nCheck_data complete!\n");
运行一下代码:
raw_num is 8raw_data is 0x11 0x22 0x11 0x11 0x11 0x34 0x34 0x22 Check_data complete!Get result_data failed!
此处Get result_data failed!
是因为runLenEncoding
函数返回的reuslt
是一个空指针。
程序调试小技巧
那我对runLenEncoding
函数内部做一点小修改,作为调试使用,查看目前为止能否完整运行程序的框架:
char *runLenEncoding(const char* raw_data, int raw_num, int* result_num){ //char* result=NULL; //作为调试,任意定义一个6个元素的数组作为结果数据空间 static char result[]={0x11,0x22,0x33,0x44,0x55,0x66}; //反向更新result_num的个数,此处为6 *result_num=6; return result;}
static修饰符
又多了一个坑:static
修饰符。因为是在函数内部定义的、用于调试的result
数组变量,而该变量在栈空间,想起前文提到的栈空间(局部变量)在函数结束返回后就销毁了吗?
为填这个坑,于是加static
修饰符,让变量成为静态局部变量,则该变量不会被销毁,生命周期是直至程序结束。
指针与*
修饰符
//通过子函数result_num指针,反向更新main函数的result_num变量*result_num=6;
指针是一个大坑,足够另外写N篇文章。
参考自《让你不再害怕指针》,链接里面有详细说明。
- 先抛出指针最核心的四个问题:
1. 指针的类型
2. 指针所指向的类型
3. 指针的值(指针所指向的地址)
4. 指针本身所占据的地址
-再简单阐释一下*
修饰符—— *p
,该运算结果多种多样。本质是“p
所指向的东西”。 *p
的特点是:
1. *p
的类型为p
所指向的类型;
2. *p
所占用的地址是p
所指向的地址。
在runLenEncoding
函数中,result_num
是一个指针,则*result_num
即为result_num所指向的东西
——main
函数中的result_num变量
。
则main
函数中的result_num
等于6.
运行结果为:
raw_num is 8raw_data is 0x11 0x22 0x11 0x11 0x11 0x34 0x34 0x22 Check_data complete!result_data is 0x11 0x22 0x33 0x44 0x55 0x66 Check_data complete!
可见,程序的大体框架可以运行了。把调试的代码注释掉。
输入数据和输出数据都搞定了,接下来是重头戏 ——写runLenEncoding函数,真正实现游程编码的函数。
大家可以看到,目前为止,我只是写了runLenEncoding函数的声明,并没有写它的实现。写代码是应该先写框架,梳理了输入和输出,才能好好着手写实现的函数。
游程编码实现函数
再抄一遍runLenEncoding
函数的框架:
char *runLenEncoding(const char* raw_data, int raw_num, int* result_num){ char* result=NULL; return result;}
需要多大结果数据空间
终于不得不面对,要给结果数据分配多大的内存空间的问题。 结果数据空间:我该如何地存在?!
回看游程编码的问题:
给定一串数据,如:
0x11,0x22,0x11,0x11,0x11,0x34,0x34,0x22
编码后,得到:
0x11,0x01,0x22,0x01,0x11,0x03,0x34,0x02,0x22,0x01
这个问题等价为:在原始空间中,从第一个元素开始逐个比较和计数。像是玩连连看,连续相同的元素则视为只占一个位置,求需要多少个位置?
相同/不相同的比较问题,显然是用if
语句处理。
- 涉及到逐个比较,需要定义一个中间变量(此处是tmp
);
- 涉及到计数,需要定义一个变量(此处是malloc_num
)来数数。
实际上,一个萝卜占两个坑。因此有:
//结果空间中:第一个坑是放原始数据,第二个坑是放相应的原始数据有多少个*result_num= malloc_num*2;
代码如下:
//找出结果数据空间所需大小 char tmp=0;//比较时候用到的中间变量 int malloc_num=1;//至少要占一个位置 tmp=raw[0];//把第一个元素赋给中间变量tmp for(int i=1;i<raw_num;i++){ if(tmp!=raw[i])//若tmp和下一个元素不相等 { malloc_num+=1;//则计数变量加1 tmp=raw[i];//更新中间变量 } } *result_num=malloc_num*2; printf("The result_num is %d\n",*result_num);
堆空间与malloc函数
终于可以为结果数据空间分配内存了。malloc一下,马上出发。
使用malloc
函数,需要在源代码前加上头文件:
#include<stdlib.h>
目测一下malloc
函数原型:
//输入参数数据类型为size_t整型,吩咐malloc给老子弄多少个字节的堆空间。//返回该堆空间的首地址//没有指定接收的指针应该怎么读,因此指针接收的时候要根据需要进行强制类型转换void* malloc(size_t);
妥,那我用一个result
指针去接收这个地址吧!
char* result=NULL;//强制类型转换为char*类型result=(char *)malloc(*result_num);
释放堆空间
malloc
函数和free
函数是配套使用的。前文提到,堆空间的生命周期是程序员说了算的,所以要记得使用完堆空间后及时释放,不然它一直占用系统资源,会出现内存泄漏!
//有借有还,再借不难//malloc后,堆空间用完就要free,释放资源//此处放在main函数中对result_data进行check_data函数后,即在main函数的最后,函数返回之前,free(result);
检查指针
每次接受地址,记得检查一下指针指对没有哦!
//malloc函数堆空间动态内存分配 result=(char*)malloc(*result_num); //检查result结果指针 if(result==NULL){ printf("malloc failed!\n"); return NULL; } printf("malloc successfully!\n");
万事具备,只差对结果数据空间的填充啦!
结果数据空间的填充
回看游程编码的问题:
给定一串数据,如:
0x11,0x22,0x11,0x11,0x11,0x34,0x34,0x22
编码后,得到:
0x11,0x01,0x22,0x01,0x11,0x03,0x34,0x02,0x22,0x01
与前文找结果数据空间大小类似的,此处同样要对原始数据进行逐个比较和计数。不过稍微复杂一些,需要对结果数据空间也要操作。
别怕,有神图护体
具体流程如下所示:
- 把原始空间的第一个元素0x11放进结果空间第一个位置;
result[0]=raw[0];
- 把原始空间的第一个元素0x11放进中间变量tmp;
tmp=raw[0];
- 原始空间中,第一个元素和第二个元素相同吗?0x11和0x22不相等,则结果空间第二个位置填count;count重新置为1;
//这里需要一个result_offset变量来作为结果数据空间的索引//result_offset=1;初始化为1if(tmp!=raw[i]){ result[result_offset]=count; //count完成阶段使命后,重新置为1 count=1; //打辅助的result_offset,在count赋值后,自增+1续一秒 result_offset++;}
- 把0x22放入结果空间第三个位置;
if(tmp!=raw[i]){ result[result_offset]=count; count=1; result_offset++; result[result_offset]=raw[i]; //result_offset完成一次辅助,则自增+1 result_offset++;}
可以改改代码,写得更精简一些:
if(tmp!=raw[i]){ result[result_offset++]=count; count=1; result[result_offset++]=raw[i];}
0x22和下一个元素0x11相等吗?0x22和0x11不相等,则结果空间第四个位置填count;
把0x11放入结果空间第五个位置,把0x11放入中间变量tmp;
if(tmp!=raw[i]){ result[result_offset++]=count; count=1; result[result_offset++]=raw[i];//有发现前面代码少了什么吗?对,发现两个元素不一样要更新tmp!//tmp用来更新被比较数,用以与下一个元素比较!!! tmp=raw[i];}
- 0x11和下一个元素0x11相等吗? 相等,则count计数加1,此处变为2.
if(tmp!=raw[i]){ result[result_offset++]=count; count=1; result[result_offset++]=raw[i]; tmp=raw[i];}//相等就是else, count自增+1else count++;
- 0x11和下一个元素相等吗?
- ……
- 以此类推
//以此类推,怎么能少得了for循环?for(int i=1;i<raw_num;i++){ if(raw[i]!=tmp){ result[result_offset++]=count; count=1; result[result_offset++]=raw[i]; tmp=raw[i]; } else count++; }
- 最后,不要忘了做收尾处理!不要忘了做收尾处理!不要忘了做收尾处理!
//容易被遗忘的收尾处理for(int i=1;i<raw_num;i++){ if(raw[i]!=tmp){ result[result_offset++]=count; count=1; result[result_offset++]=raw[i]; tmp=raw[i]; } else count++; }//在for循环退出后,把count中的值赋给result最后一位result[result_offset]=count;
到此为止,已经完成了结果数据空间的填充!
完整的runLenEncoding
函数
char *runLenEncoding(const char* raw,int raw_num,int* result_num){ char* result=NULL; char tmp=0; int malloc_num=1; int count=1; int result_offset=1;//之前用于测试的代码行 //static char result[]={0x11,0x22,0x33,0x44,0x55,0x66}; printf("Start to encode!\n");//找出结果数据空间所需大小 tmp=raw[0]; for(int i=1;i<raw_num;i++){ if(tmp!=raw[i]) { malloc_num+=1; tmp=raw[i]; } } *result_num=malloc_num*2; printf("The result_num is %d\n",*result_num);//通过malloc分配结果数据空间 result=(char*)malloc((size_t)*result_num); //check result pointer if(result==NULL){ printf("malloc failed!\n"); return NULL; } printf("malloc successfully!\n");//结果数据空间的填充 tmp=raw[0]; result[0]=raw[0]; for(int i=1;i<raw_num;i++){ if(raw[i]!=tmp){ result[result_offset++]=count; count=1; result[result_offset++]=raw[i]; tmp=raw[i]; } else count++; }//收尾处理 result[result_offset]=count; printf("Complete encoding!Start to back to main function!\n");//之前用于测试的代码行 //*result_num=6; return result;}
完整代码
经过锲而不舍的努力,终于码好代码啦!整合起来!
#include<stdio.h>#include<stdlib.h>char* runLenEncoding(const char*, int,int*);void check_data(const char*,const int);int main(){ //原始数据空间,这里简单粗暴地放在栈空间即可 char raw_data[]={0x11,0x22,0x11,0x11,0x11,0x34,0x34,0x22}; //结果数据空间(实质是指向结果数据空间的指针)。初始化指向NULL char *result_data=NULL; //原始数据的个数 int raw_num=0; //结果数据的个数 int result_num=0; //求出原始数据的个数 raw_num=sizeof(raw_data)/sizeof(raw_data[0]); printf("raw_num is %d\n",raw_num); //打印原始数据 printf("raw_data is \n"); check_data(raw_data, raw_num); //调用函数,进行游程编码,返回堆空间(结果数据空间)的首地址给main函数中的结果指针? result_data=runLenEncoding(raw_data, raw_num, &result_num); //返回指针后,对指针进行检查 if(result_data==NULL){ printf("Get result_data failed!\n"); return -1; } //打印结果数据 printf("result_data is \n"); check_data(result_data, result_num); //释放堆空间 free(result); return 0;}char *runLenEncoding(const char* raw,int raw_num,int* result_num){ char* result=NULL; char tmp=0; int malloc_num=1; int count=1; int result_offset=1;//之前用于测试的代码行 //static char result[]={0x11,0x22,0x33,0x44,0x55,0x66}; printf("Start to encode!\n");//找出结果数据空间所需大小 tmp=raw[0]; for(int i=1;i<raw_num;i++){ if(tmp!=raw[i]) { malloc_num+=1; tmp=raw[i]; } } *result_num=malloc_num*2; printf("The result_num is %d\n",*result_num);//通过malloc分配结果数据空间 result=(char*)malloc((size_t)*result_num); //check result pointer if(result==NULL){ printf("malloc failed!\n"); return NULL; } printf("malloc successfully!\n");//结果数据空间的填充 tmp=raw[0]; result[0]=raw[0]; for(int i=1;i<raw_num;i++){ if(raw[i]!=tmp){ result[result_offset++]=count; count=1; result[result_offset++]=raw[i]; tmp=raw[i]; } else count++; }//收尾处理 result[result_offset]=count; printf("Complete encoding!Start to back to main function!\n");//之前用于测试的代码行 //*result_num=6; return result;}void check_data(const char* data,const int num){ for(int i=0;i<num;i++){ printf("0x%02x\t",data[i]); } printf("\nCheck_data complete!\n");}
运行结果如下:
raw_num is 8raw_data is 0x11 0x22 0x11 0x11 0x11 0x34 0x34 0x22 Check_data complete!Start to encode!The result_num is 10malloc successfully!Complete encoding!Start to back to main function!result_data is 0x11 0x01 0x22 0x01 0x11 0x03 0x34 0x02 0x22 0x01 Check_data complete!
妥妥的!
printf
函数
在我的代码中,加入了很多printf()
函数,目的是为了方便调试debug。有这样的习惯和意识,方便debug时候快速定位bug在哪里!
等代码调试完全ok后,把不必要的、调试用的printf()
和其他函数、注释删除即可。
手把手教你用C写游程编码。作为菜鸟的我也只能帮到这儿了。别看那么多了,代码敲起来才是王道!!!代码敲起来才是王道!!!代码敲起来才是王道!!!
- 手把手教你用C写游程编码
- C语言-手把手教你写贪吃蛇AI(上)
- C语言-手把手教你写贪吃蛇AI(中)
- C语言-手把手教你写贪吃蛇AI(下)
- 手把手教你写批处理
- 手把手教你写批处理
- 手把手教你写批处理
- 手把手教你写批处理
- 手把手教你写批处理
- 手把手教你写批处理
- 手把手教你写批处理
- 手把手教你写批处理
- 手把手教你写批处理
- 手把手教你写批处理
- 手把手教你写批处理
- 手把手教你写tar
- 手把手教你写设置
- 游程编码
- About Delegate and Anonymous methods
- 联系人界面快速索引条
- crm职位部门(增删改+数据搜索)
- 12042
- 导入employee测试数据
- 手把手教你用C写游程编码
- 神箭手云爬虫平台 如何在1小时内编写简单爬虫
- springAOP中的各种通知
- split和block的区别以及maptask和reducetask个数设定
- HbaseDaoImp HBASE 增删改查
- Jquery实现多选框的基本功能
- C语言编译数组地址分配问题
- MOS管知识
- 小案例-健康栏目的实现分析