我的C语言学习历程

发表于2011年12月04日

起步篇

高二时,我有了一部学习机,它搭载的系统是嵌入式linux系统,除了学习功能外,还自带了“编程天地”功能,看名字大家应该就能知道,这个是用来编程的。我接触C语言也是从这里开始的。
 

学习机里的“趣味导学”的开头有这样一段内容:

你玩过电脑游戏吗?
你喜欢电脑游戏吗?
电脑游戏是你生活中的一部分吗?
小时候,游戏是一台黑白电视机——现实在手柄的这头,梦境在手柄的那头.
有的游戏改变过人的一生……
长大后,游戏是一枚小小的铜币——现实在摇杆的这头,梦境在摇杆的那头。
现在啊,游戏是一台电脑——现实在鼠标这头,梦境在鼠标那头……
当计算机技术给游戏提供了强有力的支持后,一个陌生而又似曾相识的新奇世界展示在人们面前。
这里有逝去的童年梦想,有心头压抑已久的情感;有疯狂、神秘、也有脑力和技巧的挑战;有轻松获得实实在在的知识,也有用“虚拟”成就一个别样的人生。这不是一个神奇的世界吗?下面,我们就用编游戏来学习C语言吧!
总是从HELLO WORLD开始。
学习编程的第一个程序,一般就是打印一个亲切的词语——“Hello,World!”
……
(后面的内容省略)

打开“编程天地”,主菜单里面有个Noah-IDE,之前还不知道IDE是什么,有空百度了一下,原来IDE指的是集成开发环境;

这个Noah-IDE很强大,虽然没有电脑端的那样多的功能,比如:语法高亮、自动缩进、代码格式化。

但是,基本功能还是具备的:可以新建C/C++工程、为工程添加源码文件、支持编辑撤销/重做、跳转到指定行、编译工程时自动生成makefile文件并make之,更想不到的是,还自带了gcc、g++编译器,busybox也有,头文件当然比较齐全。

之前看“趣味导学”里的示例源码,第一个是HelloWorld!程序,我就照着写了,测试了一下,可以显示Hello World!,之后,上面说要加个getch()函数,按任意键后退出程序,我就改了一下源码,加上头文件:conio.h,printf()后加上getch(),编译之,结果可想而知,无法通过编译,现在我是知道原因的,这个示例源码是适合windows系统的,而学习机是linux系统,与windows系统不同,有些头文件没有。“趣味导学”里面还有两个源码:在屏幕中移动的笑脸,推箱子游戏;两个都试过,由于编译不通过就放弃了。

“编程天地”里还有C语言教程,入门篇、初级篇、中级篇、高级篇,我都看过,折腾很久才会用printf()函数,什么for、while、switch – case在那时都还不会用…

某天,没事就研究学习机,但只会几个命令,用ls命令查看了bin文件夹里的文件,随便选了一个,选到了gzip,加上“—help”参数,看了帮助,英文的,还不理解,上网查了一下,gzip是个压缩软件,能将文件压缩成gz格式的压缩文件,了解了这个后,又发现了tar和bzip2,经过测试,学习机里有这些软件,并且可用。但是,我发现敲命令行太麻烦,如果有个软件能简化操作就好了,于是就有了用C语言编写压缩管理程序的想法,从这个时候开始,“压缩管理“便成为我的第一个正式的C语言项目,也是我学C语言的目的,为什么要学C语言?学C语言干什么?这两个问题也就有了答案。

当时只会基本的函数: 用printf函数显示字符菜单, scanf函数接受按键输入,if语句判断输入内容,system函数调用命令进行操作,rename函数修改文件名,打印的字符界面还是黑白的;

由于是上高中,每个月只有放月假才能回去碰电脑,其余时间在学校,C语言代码大部分是用学习机在课余时间写的,两手握着学习机,疯狂的点击屏幕….

有时偶尔去网吧,用电脑敲代码,当然,是代码比较多、需要复制粘贴才去网吧的。

之前在学习机的论坛上见到某位高人的帖子,他的程序能在终端打印彩色字符,在QQ上问了相关人士(学C的),无果,只好去百度,关键词是:“linux 彩色 字符“,终于找到了答案:http://apps.hi.baidu.com/share/detail/24241711

不久,就将它应用了到我的程序,实现了彩色字符界面(如图所示)。

这时的“压缩管理“,功能很少,只支持指定位置里的文件的压缩及解压,想解压/压缩文件,就必须把文件放到指定的文件夹下,然后运行程序,进行操作。

按键输入还是有毛病,不能一次性输入多个字符,否则,会跳过菜单显示,例如:主菜单有abcdef六个选项,每个选项都有子菜单,如果我输入ab,那么就会跳过a选项的子菜单的显示,直接执行子菜单中的b选项操作。为了解决这个问题,我用了这么个办法:scanf(“%s”,&str);,if语句判断,只判断str[0]的值,每次输入完后,清空str里的全部内容。

“压缩管理“完成后,想在论坛上发布,与其他机油分享成果,但又不想让他们轻易获得这个程序(毕竟是个人的劳动成果,有点舍不得),于是就想到了一个办法:添加试用期,正版激活。试用期为7天;

如何获取时间?

毫无疑问,我去了CSDN论坛发帖,问了这个问题,帖子:http://topic.csdn.net/u/20101016/09/13d6c4c1-8099-4fad-bc3f-7d80dc898117.html

如何知道程序安装时间?

我想, 程序第一次运行时创建一个文件,记录安装时间,以后运行就读取这个文件,要实现这个,需要用到C语言的文件操作类的函数,重新复习了一遍学习机里自带的教程,又在网上搜索了一番,我用了fprintf函数写入数据至文件,fscanf函数读取文件内容;

这时,我有了这么一个想法:printf和fprintf都是输出数据,用法基本一样,前者是将内容输出至屏幕,后者是输出至文件,fprintf前面的f是指file吗?我想应该是的,那么,是不是有个输出至字符串变量的函数?字符串的名字是string, string+printf = sprint, 会有sprintf函数吗?百度了一下,真的有,而且用法和printf、fprintf的基本一样,这时,我便学会用sprintf函数改变字符串变量的内容了,对以后的字符串处理的帮助很大。

安装时间的保存和读取的功能已经基本实现,那如何知道还剩几天呢?

如何判断是否超过了试用期?

没法,为了这个问题,有空就拿个草稿纸,用笔在上面画程序运行流程图,先干什么,后干什么,判断什么,画着画着,还是觉得直接敲代码好一些;

判断时间,我用了一堆if语句,先判断年份,后判断月份,天,小时,分钟,秒;考虑到1月、3月、5月、7月、8月、10月、12月有31天,4月、6月、9月、11月有30天,而2月在平年有28天,闰年有29天,为了准确度,只好全部判断了。(现在觉得这样有点愚蠢)

试用期的天数的计算倒是勉强完成了,可是程序的正版激活如何实现?

像电脑上的软件那样用CD-Key激活?

CD-Key如何生成?

为了防止多个人共用一个CD-Key,如何实现一个学习机上只能用专有的CD-Key激活程序?

我是这样想的:先实现独一无二的产品ID的生成,之后,通过算法算出对应的CD-Key,想激活程序的机友,可将产品ID以回帖方式发给我,我再给他CD-Key,这样就能增加帖子的回复数和人气,但是,这个功能由于本人当时技术水平有限,终究还是没实现,关于这个问题,在论坛上提过:

http://topic.csdn.net/u/20101010/13/51fb341e-05c8-46f3-919c-6b17d3955d80.html

http://topic.csdn.net/u/20101210/09/1b49f8b8-213d-4c40-8b1d-c6e3015d3ea7.html

关于这个功能,先扯远一点,某一天,在看include文件夹的头文件时,发现了uuid,很想知道这个是干什么的,百度结果是:

UUID含义是通用唯一识别码 (Universally Unique Identifier),这 是一个软件建构的标准,也是被开源软件基金会 (Open Software Foundation, OSF) 的组织在分布式计算环境 (Distributed Computing Environment, DCE) 领域的一部份。

研究了uuid.h头文件中的函数的用法,写了个测试程序,经过多次修改,生成的数字可以以这种格式显示:

xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx

这不正是我要找的能生成独一无二的产品ID的功能吗?但是,发现这个是很久以后的事了,那时可没想过再添加这个功能。

回归正题:

当时,我不会写自定义的函数,所有代码都写在main函数内,尝试过把部分功能的代码分离开,写在自定义函数内,可是不知到该如何和main函数共享数据,例如:main函数将一个变量a作为参数传给自定义的func函数,在func函数中可以把传进来的变量的值随意修改,可退出后,main函数的变量a还是没变。这问题出现后,经过纠结和思考N天后,终于解决了,比如func函数原型是:func(char a[]);在a前面加个*,再去掉[]就可以了:func(char *a); ,func函数中的a变成了char型的指针,指向传入的参数,对指针的内容进行修改,就相当于对调用func函数的上一级函数中的的变量进行修改。

上网搜索过程中,还了解了局部变量和全局变量,全局变量可以多个函数共用,局部变量只在声明它的函数内起作用。

代码可以分离成多个函数,但还是在一个c文件内,考虑到一个C文件内源代码量很多的话会影响编译耗时,于是就想将代码分离至多个文件,这样,编译耗时就不会那么长了,只重新编译被修改过的C文件中的源代码。

小结:

通过编写压缩文件管理器,使我学会了:

“压缩管理”的第二个版本,也就是0.84版(之前的版本是测试版本),加入了很多新功能。那时,由于本人发现了一个强大的压缩软件:7-zip,就把它作为“压缩管理“调用的主要程序,改用命令行版的7-zip,支持解压的格式更多,压缩文件的可自定义参数也较多,包括了系统自带的tar、gzip、bzip2这三个压缩程序的功能;

为了更好的利用7-zip,我还特意研究了7-zip的命令行用法,花费了一些时间,终于实现了”压缩管理“的压缩参数设置功能,具体如图所示:

图中所示的是7z格式的压缩参数,更新后,又添加了几个选项:

Zip格式的压缩参数:

单词大小、词典大小、压缩等级、压缩算法等参数均可自定义;7z和zip这两种格式的压缩参数最多,输入对应字母即可切换参数,源码中用的if语句也非常多,反正是根据自己的逻辑思维写的,怎么想的就用什么样的代码描述。

这些自定义的参数都保存在文件内,参数保存和读取的功能费了比较大的功夫才完成的,实现这个功能,我是这样想的:

首先,需要读取每一行字符串,这个,我在文件操作类的函数中找到了fgets函数,它能读取一行内容,要让它读取每一行内容,我就用了while循环,每读一行就进行处理;那么,如何搜索已读取的字符串是否包含指定的字符串?我用strstr函数,这个strstr函数是通过提问得到的:http://topic.csdn.net/u/20101111/09/927dccb8-0797-4e51-af0e-60dabe14f771.html

用strstr函数查找是否有对应选项,有的话,保存该选项后面的内容,例如:

有一行内容是这样的:“switch = off“,程序搜索时,就用strstr函数搜索该行是否有“switch =“,有的话,判断后面的内容是否正确,考虑到字符串首尾会有空格,在网上找了个去除字符串首尾空格的代码,这个代码是仿照trim函数写的,又考虑到字符要不分大小写的对比,例如switch和SWITCH等同,找到了strcasecmp函数。

调用7-zip进行压缩和解压文件主要是通过system函数实现的,命令行的内容,是通过sprintf函数处理生成的,先声明一个char型的字符串变量command用于存储命令行内容,每个参数都有各自的变量,当这个参数有效时就赋值给相应变量,无效则清空相应变量里的内容,最终用形如sprintf(command,”7z  %s %s %s%s %s %s %s”a,b,c,d,e,f,g)的代码生成命令行,用system(command)运行命令行。

当用7-zip解压和压缩文件时,在终端上显示的字符是英文的,于是就有了把它汉化的想法,于是,去看7-zip的源代码,源代码是C++语言编写的,有些地方和C相似,还是能看明白一部分,找到需要汉化的字符,改成中文,黑白太单调,改用了彩色字符,重新编译,这个编译,是在学习机下完成的,先./configure,后make,这是在学习机论坛上得知的linux系统下编译源码的方法,受益匪浅啊。

下面是汉化后的效果图,PC端ubuntu系统测试效果+PS合成,那时学习机出毛病送去修了,只有在电脑上测试,看字体应该看得出。

 

这个版本的“压缩管理“还是不能添加任意位置的文件压缩成压缩包,但是,却可以解压任意位置的压缩文件;

研究了学习机自带的”资源管理“的关联功能, 所有格式的文件关联信息都保存在resourcemanager.app文件中,为了更好的修改文件内容,我又用C语言编写了一个”关联管理“的程序,可以添加/编辑/删除关联数据,具体见下图,在此不做过多的说明。

 

为了更好的查看“压缩管理“与支持的压缩文件格式的关联状态,我就把”关联管理“集成到了“压缩管理“中,具体可以看图:

主菜单中直接显示已关联的格式数量,新增的关联管理功能菜单中可以显示具体状态,红色背景的表示为已关联,黄色字体表示未关联。

由于压缩7z和zip格式可以设置压缩密码,为了避免忘记密码,加了密码查询功能,可以查询之前设置的密码;在操作完成时,程序会播放提示音,音频播放的功能,我调用了madplay程序播放mp3格式的音频。

 

学习机的总内存为64MB,平常的可用内存为5MB左右, 有时调用7zip压缩文件和解压文件时会提示内存不够,为了腾出足够的内存空间供当前程序使用,需要一个SWAP分区来保存暂时没有用的程序的数据,于是就加了SWAP开关功能,SWAP这个东东也是在学习机论坛上得知的。

建立文件关联后,就可以在学习机里的“资源管理”里打开压缩文件,打开方式是调用“压缩管理”,并将文件的绝对路径作为参数传给它,int main(int argc,char *argv[])中的argc和argv就起到作用了,通过判断argc的值得出是否有参数传入,有参数传入就会保存在argv里。

有参数时,会进入文件操作菜单,这个菜单中的“位置”和“名称”内容是变化的,内容过长、过短都会影响字符界面,于是,写了个字符串处理函数,指定字符的长度,低于这个长度则用空格填补,大于这个长度则截取内容,并将末尾3个字符改为”.”。

初始形态是这样的:

可是,7-zip对压缩文件的操作不止这些,花费了一些时间完善该功能的代码,最终实现了如下图所示的效果:

传入的参数是文件的绝对路径, 如何将文件名和位置从绝对路径中分离出来?当时采用的方法是计算绝对路径中的“/”的个数,以最后一个”/”为字符串截断点,把字符串分成两个。

 

解压压缩文件时,可以进行设置,解压后自动关闭终端,返回“资源管理”;

压缩文件的释放目录可以自定义:当前目录、工作目录、自定义目录。

查看文件列表是使用了“l”参数查看压缩包内的文件,用了输出重定向,把7z打印在屏幕的文件列表重定向至文件。

压缩文件还可以再次进行压缩,考虑到tar格式的存在,就添加了这个功能, 将tar格式的文件压缩成gzip 、bzip2、zip、7z格式,可以看上图,在资源管理里面,源文件是terminfo.tar,7z和zip都是使用lzma算法压缩。

解压和压缩文件时,少不了文件路径的选择,直接输入字符很麻烦,就这样,我又新增了路径选择功能,字符界面的:

但是,选择的只是“本地磁盘”和“存储卡”中固定的文件目录。

存储卡的空间信息显示有误,后来知道是块大小的问题,我复制的源代码中,块大小直接用4,我改成了用stat结构体中的变量 st_blksize后,才正常计算了总空间大小。

曾经还用过Ncurses库里的函数实现程序启动动画,但是在学习机里不支持彩色字符显示,因为这个,就抛弃了Ncurses。

 

也记不起上面的图片究竟是不是同一个版本的“压缩管理”的截图,程序的更新比较频繁。

刚开始写C代码时,听说warning可以忽略,只重视error,偶然的机会,在网上发现了gcc编译器的一个参数,-Wall开启所有警告信息,于是乎,用了这个参数,结果,发现我的程序原来有这么多warning…

由于本人喜欢追求完美,想把所有warning消灭,花费了大量时间,百度了N次,最终还是消灭warning了,从此以后,写的代码编译时,都习惯性的给gcc编译器加个-Wall参数,看看程序有什么warning和error。

稍微提醒一下,大部分截图中能看到catchscreen  /mnt/xxxxx之类的字符,这个其实是我使用的截图命令,显示界面后,立刻终止程序运行,并使用catchscreen命令截图。

有的人或许能从截图中发现一些细节:

在某些截图中的字符菜单的右下角多出了“[X]返回”、“[返回键]退出”的字符,其实,前期版本的“压缩管理”的操作,需要靠输入字符、然后按回车才能确定操作的,而在我多次努力更新后,这种麻烦的操作已经被我抛弃,改用了直接按相应键进行操作,实现这个功能,主要是用了getch()函数;windows系统下有个getch()函数,可以无回显、无需回车即可接受按键输入的键值,在linux系统下虽然没有,但可以模拟实现getch()函数的功能,我百度了一番,关键词是“linux getch 模拟实现”,网上给的这个函数的源代码,并不完美,不支持方向键,因为会把方向键分解成多个键值,没办法,当时没打算改进它,勉强的先用了。

到了“压缩管理”的0.86版,添加了程序启动画面:

用的是QQ安装目录里的部分图像素材,外加自己PS编辑。

有人可能会问,启动画面的显示是如何实现的?

显示图像,我调用了mgaview程序,这个程序在学习机论坛上有机油发过,我也正好找到了它,可是呢,mgaview显示的图片只是临时的,如果屏幕有变动,这图片也就消失了,为了寻找解决方法,我就开始研究了mgaview的源码,刚开始研究的并不深,只是把部分代码改了一下,它的main函数我改成int view_image(char *filepath,int sec),传入图片文件的路径和显示时长即可显示,想把它编译成一个库,动态库和静态库的编译就是在这个时候开始学会的,我选择了静态库,而静态库其实是多个o文件经过打包而成的,用可用ar命令打包o文件成静态库。

这个mgaview程序,是我的程序实现图形化的一个重要的存在,如果没有它,现在可能还是敲字符版的程序,在后面会提到它的。

0.86版的“压缩管理”,功能已经比较完整,能添加的功能也尽量添加了,例如:IPK的安装、IPK安装包信息的读取、IPK安装包的制作、IPK安装信息的读取等等,IPK是学习机的安装包文件的一中格式,属于压缩文件,既然是压缩文件,我的“压缩管理”怎么能放过呢?

从初始版本到0.86版,累计写了3000+行的代码,用行数表示还是不准确,0.83版(貌似是这个版本)的代码量将近1000行,到了0.84版时,代码重新开始写,直到0.86版,一直在新增、修改代码,0.85版的总源代码的大小是126KB;到了0.86版,光是新增的7z和zip的压缩参数配置功能的源代码就占了50KB左右,其它源代码也经过修改和新增,由于现在只有0.87版的源码,之前的源码都找不到,0.86版之前的代码量也就不知道是多少了。

小结

在对压缩文件管理器的版本进行更新的过程中,经历了多次代码新增、完善、优化,从中也学到了一些知识:

了解了7z、tar、gz、bzip2命令的部分参数的用法以及功能;

for、while循环的用法;

利用string.h头文件中声明的函数,实现了基本的字符串处理;

利用system函数实现程序的调用;配置文件的数据读取与保存;

文件系统的空间信息的获取;

初步了解如何用pthred.h头文件中声明的函数来实现多线程;

linux系统中有swap分区,和windows系统的虚拟内存的功能基本一样;

gcc编译器的-Wall参数对代码语法问题的纠正起了很大作用,自从用了它后,我对我写的程序的要求是无任何警告通过编译;

以前只会gcc -c 编译源码,有些警告信息被gcc隐藏了,虽然不是很严重的警告,但这也说明我写的代码还是存在一些语法错误;

学会自己通过百度谷歌搜索问题解决方法,其实,学C语言是自己的事,需要自己独立解决自己遇到的问题,不能依赖别人,简单的语法错误和逻辑错误不要麻烦别人,在论坛上能讨论的只是程序的设计思路、工作原理、性能的改善、代码的优化;

进阶篇

到了0.87版,我已经将程序的名字改成“文件管理器”,因为那时我觉得,PC版的7-zip都有文件管理器的功能,同是压缩软件的“压缩管理”也应该具备文件管理器的功能,就这样,我又开始写代码,为了实现文件管理器。

先看看一些截图:

文件管理器0.87版的主界面直接显示空间信息,源代码修改自之前的“压缩管理”中的路径选择菜单,第一张图中显示的空间信息有误,在后来的更新中才解决掉这个问题,“本地磁盘”的背景为红色,表示当前选中的是“本地磁盘”,按下↓键可以选中“存储卡”,存储卡的检测,我是直接读取根目录下的proc文件夹中的某个文件实现的,存储卡处于移除状态,该文件内容为REMOVE,存储卡为插入状态,内容就会是INSIDE。

让getch函数支持方向键,我采用的方法是:接受第一个按键键值后,创建一个线程,用于再次接受按键键值,等1000毫秒后,终止这个线程,之后,将子线程获取的键值与刚刚得到的键值相加,就得到了一个新的键值,改进版的getch函数的源代码在这里:http://blog.csdn.net/liuchao35758600/article/details/6419499

第二张图是更新后的效果。上面一排的文字是功能选项,最初只有三个,日后的更新中新增至5个;

按F键即可显示字符下拉菜单,具体看第三张图,下拉菜单中的选项也有对应的快捷键,但在后来的更新中已经去掉快捷键了。

实现这些字符界面,使用了转义序列,具体可见此贴:http://club.noahedu.com/viewthread.php?tid=118019&highlight=%D7%AA%D2%E5

使用printf函数打印\e[y;xH,就可以将终端的光标定位到y行x列,例如:printf("\e[1;1H");将光标定位到第1行第1列,这时,后面打印的内容就会从这个位置开始显示。

要实现文件管理器的文件浏览功能,首先要能读取文件目录信息,以“linux 遍历文件目录 C”为关键词在网上搜索,找到个合适的代码就复制过来了。

windows 7系统中的资源管理器,可以显示当前文件夹下有多少文件夹和文件,我也模仿实现了这个功能,修改遍历文件目录的源代码,加入文件夹和文件计数功能。

文件列表中应该要显示文件大小,于是就用stat函数在获取文件列表后获取每个文件的大小。

文件列表中应该要按顺序显示,文件夹排前面,文件排后面,我在网上搜索了一下,发现scandir函数生成的文件列表是自动经过排序的,而这个文件列表是用malloc分配的内存存储的,需要free掉;生成的文件列表是文件和文件夹混合的,需要将文件夹和文件分离出来,用for循环判断文件列表中的文件的属性,这个时候我用的是二维数组存储的,静态内存,编译出来的文件也很大。

考虑到浏览的文件目录内的文件和文件夹会很多,用静态内存存储文件列表,存储的数量是有限的,听说有动态内存,网上搜索了一下,用malloc函数分配内存,free函数释放内存,就这样,我把存储文件列表及各个文件的大小的二维数组改成了二维指针,已知文件总数、文件夹数、文件数、每个文件的文件名、每个文件的大小,分配内存也就很好分配,用完后,用free函数释放掉,free函数要和malloc函数成对使用,用了多少次malloc函数就需要用多少次free函数。

至于文件操作,前期版本只支持单文件操作,后来的更新中,实现了多文件操作,具体如下图所示:

可以标记多个文件,标记的文件存储在二维数组中,同时用一个变量保存已标记的文件的总数。

文件管理器的界面中的左下角显示的内容是变化的,标记文件时显示“已标记N个文件”,移动/复制文件时显示"将XXX文件(或者N个项目)移动/复制至...",没有任何操作时,显示“N个文件夹,M个文件”,实现这个字符界面,主要用的是字符串处理函数,用sprintf函数生成界面每一行的内容,之后再用printf函数显示,没有用system函数调用clear命令清屏,因为这会有闪烁,影响效果,用了转义序列的光标定位功能,将要显示的内容覆盖当前显示的内容。

有的人可能会想,内容有长有短,为什么这个字符UI没有变形?

如果看过之前描述“压缩管理”的功能的那部分,应该可以知道,只是进行了字符串处理,字符串长度少于指定长度则用N个空格填补,多余则截取字符串。

有的时候截取的字符串的末尾会乱码,那是因为把汉字截成两半了,半个汉字无法显示,只能显示成框框,为了解决这个问题,我是用了一种方法:

由于GB2312编码的汉字在char型字符串是占两个字节,每个字节的值都是负数,并且,这个负数都是成对存在,这样,就能很好的处理汉字问题了。

文件的复制/移动/粘贴功能,我参考了windows 7系统中的资源管理器,毕竟它是我模仿的对象,见下图:

win7的资源管理器在复制/移动文件时会显示进度条以及相关信息,这个功能,经过思考,可以用C语言实现,用到的就是基本数学中的加减乘除。

初始形态:

经过完善后:

在复制/移动过程中,会碰到同名文件,win7的资源管理器会显示这样的界面:

要模仿实现,也很简单,用stat函数获取文件大小和修改日期,之后对比,体积大的就显示“(较大)”,最近修改的就显示“(较新)”,效果如下图所示:

文件信息查看功能,也模仿了win7的资源管理器:

使用这些功能有时会遇到段错误(Segmentation Fault),解决这些问题也用了大量时间。

在文件管理器的N次更新中,添加了文件关联功能,这样就可以调用其它程序打开文件。

这个关联信息的显示列表,我用了排序函数,把二维数组重新排序,之后再显示,排序的函数,网上搜索的,具体在哪里也不清楚了。

文件管理器包含了“压缩管理”的功能,并且,能更好的发挥7-zip的强大功能,具体见下图及其说明:

使用文件管理器,可以将多个文件压缩成压缩文件,解压文件时,可以选择任意位置作为解压目录。

第一张图是将文件压缩成压缩文件时显示的界面,可以命名压缩文件的名称以及使用的压缩格式。

第二张图是打开压缩文件时显示的界面,只有4个选项:打开、解压、更新、测试,打开就是调用7zip显示文件列表;选择解压,文件管理器会进入当前目录,可以正常浏览文件目录,按V键确定当前位置为解压位置。(看第三张图)

“压缩管理”中的压缩格式的配置功能得到完善,通过按↑键和↓键使光标">>"移动来实现选项的选择,按确定键可选中这个选项。

“压缩管理”中的IPK安装功能也进行了完善,新增了IPK安装记录,实现这个功能,我还特意研究了学习机,学习机的IPK包安装程序是ipkg-cl,通过调用它来实现IPK包的安装,安装后,会将IPK包信息记录在status文件内,我的“文件管理器”读取这个文件的内容并处理一下就可以了,具体处理方法就不说了。

在文件管理器中打开ipk安装包时,会先读取ipk包的信息,之后提示是否安装该安装包。(参考第二张图)

如果已经安装过,会提示是替换还是先卸载再安装。(参考第三张图)

可以查看这个安装包的信息,信息保存在control文件内,调用7z命令从安装包中解压指定文件,然后读取,如果没有这个文件,就不会显示安装包的信息。(参考第四张图)

安装后的IPK包,查看信息时,会显示该安装包安装后占用的空间,ipk包安装后,会自动生成ipk包内的文件列表,只需要读取这个文件列表,并累计大小即可得出占用空间大小。

小结

初步了解了如何遍历文件目录,如何获取文件名,如何获取文件的绝对路径,但还是需要借助网络。

了解了7z命令的更多参数的功能及用法。

接触了指针,因为考虑到文件目录内的文件时多时少,为了减少不必要的内存开销,就使用了指针,用malloc函数实现动态内存分配,用free函数释放内存,但有时还是出现段错误(Segmentation Fault)。

进一步掌握了转义字符的用法。

修改了网友分享的getch函数,这个getch函数是模拟实现的,为的是实现无回显接受按键输入。

发现线程间的全局变量不同步,子线程频繁修改全局变量,而主线程获取的全局变量的值不与子线程修改的值同步。

还没接触结构体,结构体是在高三暑期的时候学到的。

图形篇

在编写文件管理器的过程中,偶尔做些别的,因为代码写多了,会感到枯燥无味,没有动力,想做其它事情,于是,我就去研究mgaview的源码,因为它能显示图形,想知道它的原理,这个是研究后成果:http://blog.csdn.net/liuchao35758600/article/details/6789910,在此不做过多说明。

研究mgaview的源码的收获还是很大的,了解了linux下显示图形的方法,mgaview是通过往framebuffer(帧缓冲)写数据而实现图形显示的,具体内容不多说,可以参考那文章。

研究后,我就尝试了一下,将屏幕填充白色,之后,在自定的位置显示一个红色矩形,结果成功了,我又改了一下代码,让这个红色矩形在白色背景中移动;

在这之后,我将红色矩形改成了小鸟,小鸟的图片数组是用mgaview中的图片解码功能解码出来的,第一次测试,小鸟的图片变形了,后来发现其实是图片尺寸有问题,不是实际尺寸,纠正后能正常显示,我增加了1只小鸟,又把白色背景改成一半黑一半白的:

图片可以在程序里内置,只需要转换成图片数组即可。

你可以将屏幕看成一个坐标系,左上角是原点(0,0),由于这些图形数据是线性存储的,将二维坐标转换成一维的线性坐标的公式为:

数组中的位置 = y轴坐标 * 屏幕宽度 + x轴坐标

例如:屏幕上有个点的坐标为(320, 240),而屏幕尺寸是宽为1280像素,高为800像素,那么,这个点在数组中的位置是:240 * 1280 + 320 = 307520;

屏幕每个像素点表示的颜色都保存在一个数组中,每隔3个元素表示一个像素点的颜色,因为分别是表示Red、Green、Blue这三色的深度的,但一般是每隔4个元素,貌似多出来的是alpha通道,也就是透明度,因此,需要乘以4,才能得到真正的位置:(240 * 1280 + 320)*4 = 1 230 080;

假设指向framebuffer内存地址的指针是p_fb,那么,p_fb[1230080]是这个像素点的Red颜色深度,p_fb[1230081]是Green颜色深度,p_fb[1230082]是Blue颜色深度。

我的学习机里的像素点的颜色排列顺序是BGR,不是RGB,要显示正确的颜色,就要改变一下RGB的写入顺序,否则,显示出来的颜色不对。

掌握了这个知识,我用了彩色背景、一个人的图像素材,这个图像素材是我用手机截取的,运行游戏,之后截图,把动画分解成多个图片,之后用PS将人物动作图形抠出来,保存为png,将这些图片转换成数组,每张图一共四个数组,分别保存R、G、B、A通道,关于用alpha通道混合图形的方法,我发了个帖子:http://topic.csdn.net/u/20110623/14/ac5fbbff-a3f9-469f-a362-ecde8aa32a3f.html

用了回帖者告诉我的公式,成功的实现了用alpha通道混合图形,最终,做了个测试程序:

可用按键控制这个人行走,按J键使用攻击动作。

人物的图形素材,来自这个游戏:

我将我从mgaview源码中获得的知识,应用到了文件管理器,使之实现了半图形化界面,主界面是第一个实现图形化的:

第一张图是初始的样子,过了不久,我又改了一下,界面下面一排显示内存信息,并且会动态更新数据,这些文字是用ps做的,数字0-9,小数点、MB、GB、KB分别保存至png图片中,包含alpha通道,其余直接写个完整的文本保存至png图片。文字的显示,我通过判断字符串的内容,来生成对应的图形,之后,粘贴至指定的位置,例如:59.16

MB,遍历这个字符串,计算所需矩形背景的长度和宽度,之后判断,第一个是5,那么,用5的位图,贴到矩形背景中,第二个是9,那么,用9的的位图,贴到矩形背景中,就这样,以此类推,最终,矩形背景中就贴上了该字符串对应的位图。

容量条的显示,我准备了三张图片,红色、蓝色的满格容量条的图片,一张容量条的空槽的图片,根据百分比的值,将适合长度的满格容量条贴到空槽的图片中,超过一定值时,就用红色的容量条的图片,这个方法是那个时候想到的,当然,现在如果再来做的话,就用这种方法:准备1个像素点宽度的红蓝容量条,空槽可以用算法画,之后,根据百分比的值,画长度为 (总长度x百分比) 的容量条至空槽中。

在这之后,其它的功能也实现了图形化:

按钮是模拟实现的,虽然按下时有凹陷的效果,但都是用图片贴出来的;“窗口”中的内容,大部分是用PS预先编辑好的,剩下的由程序处理显示。

以上讲的是文件管理器的主要功能,其它细节功能就没有做过多的说明。

图形处理,使用的算法是个人经过思考和多次测试完善得出来的,比如:图形的截取,图形的合成,这些“算法”都很简单,不知道称之为“算法”是否合适。

小结

研究了mgaview程序的源代码,初步掌握了如何利用图形库提供的函数实现相应格式的图片文件的解码,bmp图片可以直接读,不需要其它图形库的支持。

初步掌握了图形数据的处理,实现了简单的图形合成,图形裁剪。

游戏篇

由于知道了如何显示图形,就产生了一种想法:做一个2D图形游戏。

游戏呢,我采用了flash小游戏《死神vs火影》中的图形素材,网上有提供。

学习机的可用内存并不多,只能节约使用内存了,图形的显示,我采用的方法是:

调用相关动作的函数,获取指定帧的图形数组,画到背景图上后,直接释放,这时,占用的内存只是一帧图形所占用的内存。

测试了一下,用的是黑崎一护的站立动画,背景是全白:

添加了移动动作的动画后,考虑到只有人物朝向右边的图形,没有朝向左边的,如果用PS编辑,做个朝向左边的的图形,需要花费很多时间,于是就想要个图形水平翻转算法,自己用脑子想了一下,并不难,只需要将图片每一行的像素点左右置换即可,水平翻转图形的代码在这里:http://blog.csdn.net/liuchao35758600/article/details/6685536

在测试这个程序时,人物移动到屏幕边缘会出现问题,显示不正常,经过大量时间仔细研究后,最终解决了这个问题,效果图如下:

即使显示在屏幕左上角,也不会出现显示问题,显示出问题也就是数据处理方法有问题,超出屏幕显示范围的图形,进行裁剪就可以了。

2D游戏的图形处理,大多涉及到图形处理算法方面的知识,比如:图形混合,图形裁剪。

在这之后,只要有空,并且有兴趣,就会继续编写这个游戏,直到最近几个月暂停了游戏编写,因为我想写个自己的GUI库;

这是游戏现在的效果:

背景图是我用PS编辑而成,费了一点时间,至于文字,也是预先用PS编辑好的。

“按确定键继续”这个文本背后的红色光标,具备闪烁动画效果,是通过调整红色光标的透明度实现的,alpha的值从0增加到255,为淡入效果,相反,由255减少到0,为淡出效果,两种效果往复使用,就变成了闪烁动画效果。

按确定键后,整个画面会使用淡出效果,然后使用淡入效果进入主菜单,选择选项用的光标还是用了那个具有闪烁动画效果的红色光标,在移动光标时,我使用了平移动画,光标一边闪烁,一边平移。

开始游戏后,就是上图的效果,起初,是没有人物状态栏的,这是在后面添加的,添加状态栏后,游戏画面的刷新速度变慢了一点,不知道有什么办法提高图形刷新的效率。

黑崎一护使用月牙天冲,原始的动作动画是一次性释放一个,我觉得这个太死了,就把人物动作和技能效果图分离出来,单独处理显示,按住U键,每隔一段时间释放一次月牙天冲,在释放完后,人物可以移动,可以与它接触。

游戏还没添加人工智能,攻击与受攻击的处理,现在来看这个游戏的源码,还是觉得有些代码写得不怎么样,打算以后把游戏的源码做大部分的修改。

以前玩过的游戏(大多是网游),脑子里都会有一些想法,觉得这个游戏可以改进的地方还有很多,有时非常希望游戏开发公司能根据我的建议来改进游戏,当然,这是不可能的,与其要求他们改变他们的游戏,还不如自己写游戏,按照自己的想法来设计游戏,这不是很好吗?

现在,用C语言完全可以自己写2D游戏,写好核心代码,调用图形进行显示就可以了,写程序,自己的思想是很重要的,用正确的代码来描述自己的思想,才能得到自己想要的结果;编译时出现错误或警告,那是语法错误,很容易解决,而程序在运行过程中出现的错误,那是代码逻辑问题,需要自己调试,寻找出错位置,这是比较难的,也是比较费脑力的,我曾经为了解决一个小问题,花费了一整天的时间去测试才得到解决。