我的 C 语言学习历程

2011年12月04日  ·  12469 字  ·  阅读

前言

这篇文章整理自作者 2011 年 12 月在 CSDN 论坛发表的《我的C语言学习历程》一贴,由于当时作者写作水平有限,想到什么就写什么,内容并未经过认真的组织,因此内容质量比较差,现在看这内容时还是挺让人尴尬的,比较庆幸的是当时看完帖子的人都没有喷作者的帖子写得烂。

入门

高二时我有了一部学习机,它搭载的是 linux 系统,除了学习功能外还自带了“编程天地”功能:

主屏幕编程天地

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

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

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

编程天地

这个 Noah-IDE 很强大,虽然它的编辑器只是个普通的文本编辑器,没有现代编辑器的语法高亮、自动缩进等功能,但也够用,可以新建 C/C++ 工程、编译工程时会自动生成 Makefile 文件并执行 make 命令,系统环境预置了 gcc、g++ 编译器和标准库头文件。

一开始是照着“趣味导学”里的给的 Helloworld! 示例源码写,经测试可以显示 Hello World!。之后,上面说要加个 getch() 函数让程序在按任意键后退出,我就改了一下源码,加上头文件 conio.h,在 printf() 后加上 getch(),改完后编译,结果编译报错,原因是学习机的 linux 系统环境中并不包含 conio.h 这种非标准库的头文件,当时没管这么多就继续找下个示例程序来测试了,然而后面的“在屏幕中移动的笑脸”和“推箱子游戏”这两个示例源码也编译报错。

“编程天地”里还有C语言教程,包括入门篇、初级篇、中级篇、高级篇,虽然看过好几遍,但写起来还是挺费劲的,折腾了很久才会用 printf() 函数,for、while、switch–case 在那时都还不会用。

第一个项目

某天,像往常一样开始折腾学习机,用 ls 命令查看了 bin 文件夹里的文件,随便选了一个,选到了 gzip,加上 --help 参数输出帮助内容,可那时并不能看懂这堆英文,在网上搜索相关信息后得知 gzip 是个压缩软件,它能将文件压缩成 gz 格式的压缩文件,在了解了这个后又发现了 tar 和 bzip2,经过测试,学习机里有这些软件并且可用,但我觉得手动敲命令行太麻烦,如果有个软件能简化操作就好了,于是就有了用 C 语言编写压缩管理程序的想法,从这个时候开始,“压缩管理“便成为我的第一个正式的 C 语言项目,也是我学 C 语言的第一个小目标。

当时只会基本的函数:用 printf() 函数显示字符菜单,scanf() 函数接受按键输入,if 语句判断输入内容,system() 函数调用命令进行操作,rename() 函数修改文件名,打印的字符界面还是黑白的。在校期间接触电脑的机会很少,大部分的 C 语言代码是用学习机写的,有时要写的代码比较多,会去网吧用电脑写代码,复制粘贴一把梭,编码效率比学习机高很多。

之前在学习机的论坛上看到某位高人的帖子,他的程序能在终端打印彩色字符,于是在 QQ 上问了编程相关人士,可没问出什么结果,只好搜索关键词 “linux 彩色 字符“,经过一番搜索后才找到答案,然后花了点时间将它应用到我的程序,最终实现了彩色字符界面:

编程天地

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

按键输入功能有问题,不能一次性输入多个字符,否则会跳过菜单显示,例如:主菜单有 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 = sprintf, 会有 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,这样能增加帖子的回复数和人气,但是,这个功能由于本人当时技术水平有限,终究还是没实现,关于这个问题,在论坛上提过:

UUID

某一天在看 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 还是没变。这问题在经过一段时间的思考后终于解决了,比如 func() 函数原型是:func(char a[]); 在 a 前面加个 *,再去掉 [] 就可以了:func(char *a);,func() 函数中的 a 变成了 char 型的指针,指向传入的参数,对指针的内容进行修改,就相当于对调用 func() 函数的上一级函数中的的变量进行修改。

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

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

调用 7-zip

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

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

压缩设置

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

编程天地

Zip 格式的压缩参数:

编程天地

单词大小、词典大小、压缩等级、压缩算法等参数均可自定义,7z 和 zip 这两种格式的压缩参数最多,输入对应字母即可切换参数,这些自定义的参数都保存在文件内,实现思路如下:

首先,需要读取每一行字符串,这个,我在文件操作类的函数中找到了 fgets() 函数,它只能读取一行内容,要让它读取每一行内容,我就用了 while 循环,每读一行就进行处理。那么,如何搜索已读取的字符串是否包含指定的字符串?我用的是 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 在解压和压缩文件时输出的文字是英文的,想把它汉化,于是就去看 7-zip 的源代码,源代码是 C++ 语言编写的,有些地方和 C 相似,还是能看明白一部分,汉化很简单,找到需要汉化的字符然后改成中文即可。黑白太单调,加上了彩色字符,改完后重新编译。这个编译是在学习机下完成的,先运行 ./configure,然后运行 make,这是在学习机论坛上得知的 linux 系统下编译源码的方法,受益匪浅。

以下是汉化后的效果图,由于当时的学习机出毛病送去修了,只能在电脑上测试,这图是用 PC 端 ubuntu 系统的输出结果合成的。

汉化效果

这个版本的压缩管理只能解压任意位置的压缩文件,还不能将任意位置的文件压缩成压缩包。

添加文件关联界面

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

关联管理

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

关联管理

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

关联管理

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

关联管理

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

SWAP

添加文件操作界面

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

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

文件操作

7-zip 对压缩文件的操作不止这些,后续我又追加了一些功能:

文件操作

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

SWAP

以下是每个设置项的说明:

  • 解压后自动关闭终端,返回“资源管理”。
  • 自定义压缩文件的释放目录:当前目录、工作目录、自定义目录。
  • 查看文件列表是使用了“l”参数查看压缩包内的文件,把 7z 输出的文件列表重定向至文件中。

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

tar 压缩

添加路径选择界面

解压和压缩文件时少不了文件路径的选择,直接输入字符很麻烦,于是我新增了路径选择功能,选择后会将文件输出到“本地磁盘”或“存储卡”中固定的目录中。

解压路径选择

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

清除编译警告

刚开始写 C 代码时,听说 warning 可以忽略,只需重视 error,然而一次偶然的机会,在网上发现了 gcc 编译器的一个参数,-Wall 开启所有警告信息,于是就试着用上这个参数,结果发现我的程序原来有这么多 warning。由于本人喜欢追求完美,想消灭掉所有 warning,于是花费了大量时间在处理 warning 代码上。从此以后,编译代码前都会给 gcc 编译器加个 -Wall 参数,看看程序有什么 warning 和 error。

改进按键操作体验

早期版本的“压缩管理”的操作方式是输入字符并按回车,一个操作需要按两次键,体验比较差,那么有没有能在按下键时立即响应的方法呢?Windows 中的 getch() 函数正好满足需求,然而它并不是标准库的函数,不适用于 linux 系统,只能想办法模拟实现 getch() 函数的功能,用关键词“linux getch 模拟实现”搜索了一番,网上给的源代码差强人意,按下方向键时会产生多个键值,当时没打算改进它,勉强的先用着了。

图形化启动画面

“压缩管理”的 0.86 版中加入了程序启动画面,这个界面只是一张图片,图片显示功能是通过调用 mgaview 程序得。

图形化启动界面

mgaview 程序是我在逛学习机论坛时上发现的,它可以打开一张图片并绘制到屏幕上,但它只绘制一次,如果屏幕内容有变动,这图片也就消失了。为了寻找解决方法,我便开始研究 mgaview 的源码,一开始研究得并不深,只是把部分代码改了一下,例如将它的 main() 函数改成 int view_image(char *filepath, int sec),以实现自定义文件路径和显示时长。后来想把它编译成一个库,在了解到动态库和静态库相关概念后,我选择了静态库,静态库其实是多个 o 文件经过打包而成的,可通过调用 ar 命令将 o 文件打包成静态库。

这个时候的“压缩管理”的功能已经比较完整,能添加的功能也尽量添加了,例如:IPK 的安装、IPK 安装包信息的读取、IPK 安装包的制作、IPK 安装信息的读取等。IPK 是学习机的的安装包文件格式,实质上也是个压缩包,这让“压缩管理”也有发挥的余地。

进阶

到了 0.87 版,程序的名字已经改成“文件管理器”,因为那时我觉得 PC 版的 7-zip 都有文件管理器的功能,同是压缩软件的“压缩管理”也应该具备文件管理器的功能。

文件管理器

这个版本中,主界面直接显示存储空间信息,源代码修改自之前的“压缩管理”中的路径选择菜单。“本地磁盘”的背景为红色,表示当前选中的是“本地磁盘”,按方向键 键可以选中“存储卡”,存储卡的插入/拔出状态判断是通过读取根目录下的 proc 文件夹中的某个文件实现的,当存储卡处于移除状态时,该文件内容为 REMOVE,处于插入状态时内容就会是 INSIDE。

为了改进 getch() 函数的方向键支持,我采用了这种做法:接受第一个按键键值后,创建一个线程,用于再次接受按键键值,等 1000 毫秒后终止这个线程,之后,将子线程获取的键值与刚刚得到的键值相加,就得到了一个新的键值。

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

文件管理器

这些字符界面是用转义序列实现,使用 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() 函数成对使用。

文件批量操作

前期版本只支持单文件操作,在后续的更新中,已经实现了多文件操作。

文件批量操作

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

字符界面重绘优化

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

文件名的长度有长有短,为了不让字符界面被文件名打乱,在输出文件名前会做些处理,当字符串长度少于指定长度则用空格填补,多余则截取字符串。

有的时候截断的字符串的末尾会乱码,原因是本地编码下的汉字占用两个字节,少了一个字节会使这个字符无效,而无效字符则会显示成框框。考虑到汉字每个字节的值都是负数,并且这个负数都是成对存在,那么在截取字符串时可以顺便统计汉字的占用字节数,当为偶数时可截断。

显示文件操作进度

windows 7 的资源管理器在复制/移动文件时会显示进度条以及相关信息:

文件操作进度

这个功能实现起来比较容易,最初的效果是这样的:

文件操作进度

经过完善后:

文件操作进度

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

文件冲突处理

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

文件冲突处理

显示文件详情

windows 7 的文件详情窗口中展示了文件名、类型、位置、大小、包含的文件数、创建时间属性:

文件信息查看

模仿效果:

文件信息查看

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

添加文件关联管理

经过多次更新后,添加了文件关联功能,这样就可以自定义文件打开方式了。

文件关联管理文件关联管理

为了方便查找,我用了一个从网上找到的排序函数对这个列表做了排序。

添加压缩管理

文件管理器中的压缩管理功能可以将多个文件压缩成压缩文件,在解压文件时,可以选择任意位置作为解压目录。

将文件压缩成压缩文件时,可以设置压缩文件的名称和压缩格式:

压缩管理

打开压缩文件后,有4个选项:

  • 打开:调用 7zip 显示文件列表
  • 解压:进入当前目录,可以正常浏览文件目录,按 V 键确定当前位置为解压位置
  • 更新:选择其他文件,然后将它们添加至压缩包内
  • 测试:调用 7zip 自带的测试功能

压缩管理压缩管理

改进配置界面

通过按 键和 键移动光标 “»” 来选中选项,按确定键可选中这个选项。

配置界面配置界面

改进 IPK 安装功能

学习机的 IPK 包安装程序是 ipkg-cl,可调用它安装 IPK 包,在安装后,它会将 IPK 包信息记录在 status 文件内,“文件管理器”可以通过读取这个文件来获取安装信息。

IPK 安装

在文件管理器中打开 ipk 安装包时,会先读取 ipk 包的信息,之后提示是否安装该安装包。

IPK 安装

如果已经安装过,会提示是替换还是先卸载再安装。

IPK 安装

安装包的信息保存在 control 文件内,可调用 7z 命令从安装包中解压该文件,然后读取内容,如果没有这个文件,则不显示安装包的信息。

IPK 安装

在安装 ipk 包后再次在查看安装包信息时,会显示该安装包安装后占用空间。占用空间的计算方法比较简单,由于 ipk 包在安装后会自动生成包内的文件列表,因此可通过读取它来累计所有文件的大小作为占用空间大小。

图形编程

一直做同样的事情容易感到乏味,这时会想找点其它事情做,比如去研究 mgaview 的源码,搞清楚它是如何将图形绘制到屏幕上的,至于研究过程就不在此处做详细描述了,研究成果可在另一篇文章中找到。

mgaview 是通过往帧缓冲 (FrameBuffer) 写入数据来实现图形显示的,在参考相关代码和帧缓存的一些文章后,开始尝试编写一个测试程序。首先,将屏幕填充白色并在指定位置绘制一个红色矩形,然后让这个红色矩形在白色背景中移动,看上去没什么难度,只是简单的纯色填充,接下来是将这个红色矩形更换为小鸟图片,小鸟的图片数组是用 mgaview 中的图片解码功能解码出来的,第一次测试,小鸟的图片变形了,后来发现是图片尺寸的问题,不是实际尺寸,纠正后能正常显示。

在这之后,我又增加了一只小鸟,并把白色背景改成一半黑一半白的:

小鸟

在屏幕坐标系中,原点(0,0)在左上角,由于像素数据是线性存储的,将二维坐标转换成一维的线性坐标的公式为:

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

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

以 RGBA32 格式存储像素数据的话,一个像素点占用四个字节,其中 Red、Green、Blue、Alpha 各占一个字节,一个字节能表达 0~255 范围的值,那么 RGB 占的三个字节能表达 16777216 种颜色,在当时能显示这么多颜色的屏幕是真彩屏幕。

第一个图形游戏

在掌握了这些图形知识之后,准备编写一个小游戏,游戏很简单,只有一张彩色背景和一个人物贴图,人物贴图素材是用手机运行游戏一帧帧截取的,截取完后还需要用图像编辑工具将人物动作图形抠出来并保存为 png。准备好图片素材后再将这些图片转换成数组,每张图一共四个数组,分别保存R、G、B、A通道,关于 alpha 通道混合图形的方法,我发了个帖子:http://topic.csdn.net/u/20110623/14/ac5fbbff-a3f9-469f-a362-ecde8aa32a3f.html

以下是最终运行效果:

简单的按键控制游戏简单的按键控制游戏

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

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

酷柚

图形化界面

经历了几个图形测试程序的开发,自认为对图形绘制方法掌握的得差不多了,于是就对文件管理器进行图形化改造,主界面是第一个实现图形化的:

文件管理器

之后又对主界面做了些改动,在界面下面增加显内存信息,并且会动态更新。

文件管理器

当然,这图形界面都是由一些预先编辑好的图片组成的,其中文字的绘制比较复杂,需要预先用图像编辑工具将数字、小数点、MB、GB、KB 分别保存至 png 图片中,然后在程序中将文字与这些图片建立映射关系,之后在绘制文字时遍历字符串将对应的图片粘贴至指定的位置,例如:59.16MB,先遍历这个字符串,计算所需矩形背景的长度和宽度,之后判断,第一个是 5,将 5 的位图贴到矩形背景中,第二个是 9,将 9 的位图贴到矩形背景中,以此类推,最后,矩形背景中就贴上了该字符串对应的位图。

容量条用到了三张图片:红色、蓝色的满格容量条的图片,一张容量条的空槽的图片。绘制时根据百分比将适合长度的满格容量条贴到空槽的图片中,当超过一定值时,就用红色容量条的图片。

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

文件管理器文件管理器文件管理器文件管理器

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

游戏编程

学会显示图形后,想做一个 2D 图形游戏,当时有了解到 flash 小游戏《死神vs火影》,网上也有相关图形素材下载,于是就决定在游戏中使用这些图形。

第一个测试程序,用的是黑崎一护的站立动画,全白背景:

游戏画面

在添加了移动动作的动画后,考虑到只有人物朝向右边的图形,没有朝向左边的,如果用图像编辑工具做个朝向左边的的版本的话比较耗时,于是就想要一个图形水平翻转算法。稍加思考后,水平翻转只是将左右像素点交换,实现起来不难。

将人物移动到屏幕边缘时显示不正常,后来发现是将屏幕范围外的像素写入到屏幕内了,解决方法是将有效区域裁剪出来再绘制。处理后的效果如下:

游戏画面

之后加上了首屏界面和主菜单:

游戏画面游戏画面

这时界面中已经有了一些简单的动画:“按确定键继续”的闪烁背景、进入主菜单时的淡入淡出、菜单光标的平移。

选择开始游戏后,会进入对战画面:

游戏画面

在添加人物状态栏之前游戏画面刷新率还能接受,然而在添加后画面刷新率有明显下降,看来学习机的硬件性能已达极限,无法支撑更加复杂的游戏画面。

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

这时的游戏还没有电脑玩家、攻击和受击判定,源代码写得也很烂,需要重构一遍。

以前玩游戏的时候都会有一些想法,觉得它有很多可改进的地方,有时还给官方反馈过意见,然而这并没有什么用,游戏官方可不会因为平民玩家的建议而对游戏做改动。现在的我,已经具备基本的游戏开发能力,可以尝试开发属于自己的游戏了。

结语

当时作者存在以下问题:

  • 大部分时间都在写代码,技术成长缓慢
  • 看过的技术书籍很少,理论基础薄弱,了解到的技术仅限于自己开发这几个程序
  • 对行业内的包括技术社区、开源项目等在内的资源了解有限,没有研究过 SDL、Qt、DirectFB、GTK 等开发库

这种水平只能算个业余编程爱好者,对找工作没有优势,如果你是一个对编程感兴趣、想以此为职业的人,建议你制定类似于以下的成长规划:

  • 在求职平台上找自己感兴趣的职位,了解自己需要具备哪些技能
  • 阅读相关技术书籍,巩固理论基础
  • 上技术成长平台刷算法题,提高算法功底
  • 找一些自己感兴趣开源项目,研究其源代码并试着改进它
  • 应用自己所学到的技术开发一个开源项目,并持续完善它
  • 写博客记录学习历程
  • 学习他人分享的面试经历,刷面试题

如果你这些都做到了,那么简历上就可以这样写:

  • 熟悉 XXXX、 XXXX、 XXXX、 XXXX
  • 熟悉常用的数据结构及算法,基础扎实
  • 具备扎实的计算机科学基础知识,熟悉各种 XXXX 原理及相关知识
  • 具备丰富的开源项目开发和维护经验,参与过 XXXX 知名开源项目并贡献过代码
  • 熟练使用 Git 版本管理工具进行多人协作开发
  • 良好的编码和文档习惯,对代码美感的追求孜孜不倦
  • 热爱学习和分享,持续更新博客 N 年以上

有了这些内容,在应届毕业生中已经有很大优势了。

文章版权归作者所有,未经许可不得转载。

问题反馈

对此文章有疑问?你可以点击 这里 反馈