内容简介:以前学C语言的时候,写过几个小程序,还算蛮有意思的。先上程序截图,占个坑,然后再慢慢讲做这种小玩意的通用思路。在这里我们不谈软件架构神马的专业知识,就站在入门水平能理解的角度思考,我觉得可以分为5个部分:
以前学 C语言 的时候,写过几个小程序,还算蛮有意思的。先上程序截图,占个坑,然后再慢慢讲做这种小玩意的通用思路。
温馨提示:亮点在最后
1、贪吃蛇:
2、都市浮生记(以前有一个很老的小游戏叫“北京浮生记”,仿那个写的,去各种地方买卖商品):
3、背单词的软件(当年女朋友刚考上英语专业,写给女朋友记单词用的,然而被各种手机APP秒杀了,说实在的,如果不考虑界面的话,我觉得我这个功能还是蛮强大的……)
4、C语言结合WindosAPI实现的图形界面闹钟
首先我们需要知道,一款软件究竟有哪几个部分?
在这里我们不谈软件架构神马的专业知识,就站在入门水平能理解的角度思考,我觉得可以分为5个部分:
1、业务逻辑
指的是解决具体问题的思路。比如做一款背单词软件,你怎么随机抽取单词,用什么规则去判断用户是否掌握了这个单词,这就是业务算法。
2、控制算法
控制逻辑是除了业务逻辑之外,关于整体程序控制层面的算法,比如怎么去实现一个链表,怎么去实现图的搜索,或者如何处理线程同步,等等。
3、人机交互
简单来说就是界面。比如C语言的控制台(“黑框框”)最基本的人机交互就是输入和输出。图形化界面就复杂得多,标签、输入框、按钮、图形绘制、事件监听等等。如果做移动开发,还可能涉及到各种传感器。
4、数据存储
小程序不需要外部的数据存储,只有程序内部的变量、常量、静态数值。想要功能丰富一点,比如小游戏的排行榜、单词软件的单词库等等,就需要考虑数据存储的问题。简单一点可以用基本的文件读写,自己规定数据存储的格式。复杂一点就需要用到数据库了。
5、网络通信
普通单机程序用不到网络通信。但如果要做网络程序,比如局域网对战游戏、CS结构的企业管理软件、BS结构的商城平台,等等,就需要考虑网络通信的功能。有各种网络协议,底层一点可以是TCP/IP,往上走的话有封装好的Socket接口,再往上走还有HTTP、FTP等等具体的应用协议。
梳理清楚这五个部分,我们再来看看,入门阶段我们学C语言学了什么?
首先是基础的程序语言知识,从输入输出、变量、分支语句、循环语句,到数组、函数、指针、结构体、文件读写,基本就学完了。
然后可能还接触了一些简单的算法和数据结构,比如 排序 、递归、栈、队列等等。再复杂一些,可能会接触树的遍历、图的搜索、甚至是动态规划。
我们看看这些知识属于哪些模块?
1、它们解决业务逻辑不成问题,毕竟我们做的很多习题,都是真实情境抽象出来的算法。
2、它能解决一部分简单的控制逻辑。这主要看你算法与数据结构学的如何。当然,涉及到 设计模式 、多线程、事件监听、以及系统层面的控制内容,我们还没学到。
3、人机交互,只学了简单的输入输出。
4、数据存储,可以用文件读写。
5、网络通信,暂时没接触。
接下来,我们只需要有针对性的弥补这些模块,找到解决方案,就能做出有趣的应用。
1、业务算法
这个不需要额外的技术了,入门阶段学到的知识基本够用,但我们要学会归纳项目需求,并把它们抽象出来,转化为平常做的习题的形式,“能获取什么数据、进行怎样的计算、要得到什么结果”。当然了,思考的时候并不是这个顺序,而是“要得到什么结果,需要什么数据,要进行怎样的计算”。
2、控制逻辑
前面说到,首先这需要你的算法与数据结构基础。至少要学会数组、结构体、排序、链表、递归等等,掌握得越多,这块就越轻松一些。当然了,这毕竟不是竞赛,自己做项目实践的时候,没有人强制规定你“在1s内完成,内存空间不超过65535KB”,所以哪怕入门阶段会的少,效率低一些,也没关系,首先做到“能用”,再考虑优化。
那么复杂一些的控制逻辑问题怎么处理呢?
①多线程
需要调用系统接口。以windows系统为例,需要调用WindosAPI,也就是windows.h库中的函数。初学阶段,我们可以“不知其所以然”,会套用就行。
举例:
问题情境:在贪吃蛇游戏中,我们需要一遍不停的让蛇向当前的方向移动,一边获取用户输入的控制信息。我们知道,C语言在使用任何一个输入函数的时候,都会等待用户的输入,然后再进行下面的语句。所以我们必须在一个单独的线程里监听用户的输入,否则会导致“用户不输入内容,蛇就不移动”的情况。
实现方法(部分代码):
#include <stdio.h> #include <windows.h> #include <conio.h> char c;//存储用户输入的按键字符的全局变量。 DWORD WINAPI getOrder();//子线程调用的方法,用来等待用户输入控制命令 int main() { CreateThread(NULL,NULL,getOrder,NULL,0,NULL); while(1){ //控制贪吃蛇不停的移动 switch(c){ //处理wsad四个字符的情况,像上下左右移动 } } return 0; } DWORD WINAPI getOrder(){ while(1){ c=getch();//不停的等待用户的输入 //此处默认用户按的肯定是wsad四个按键,没有处理错误情况。真正写代码需要考虑。 } }
此处关于多线程的部分,是我当年写贪吃蛇程序时,临时上网搜索,直接按人家的格式套用的。说实话,我到现在也不明白CreateThread里面的几个NULL和0分别需要设置什么(后来深入研究 Java 去了,一入Java深似海,没再深究C语言WindowsAPI的问题)。
至于说CreateThread不稳定不安全,实际编程里不推荐使用,而是要用_beginthread。对于初学阶段,这有什么关系呢?就像我们小学、初中学数学的时候,课本里也把很多概念简化了,并不严谨。我们使用它,是为了帮助我们迈过项目实践里的拦路虎,实现自己想要的功能,真要是以后打算深入研究,再搞明白“为什么”、“什么好”也不迟。(当然了,如果愿意多花一些时间,按网上的说法,去学习_beginthread怎么使用,一步到位,也没有问题,此处给个链接: C语言多线程编程windows多线程CreateThread与_beginthreadex本质区别 )。
②实现一些与操作系统相关的功能
这个当然也可以通过WindowsAPI来实现。但还是那句话,初学阶段,没有必要。说起来有个更简单的方法,只要会用system("");函数就行了。别看一个小小的system函数,通过它,我们可以让系统执行各种dos命令,什么开机关机,文件删查,都不在话下。
当然了,要玩转system函数也有些技巧。首先是要学会拼接字符串,比如我们要实现定时关机的命令,让用户输入一个时间,我们就要把时间数字转换成字符串,再拼接到命令里面。
样例代码如下:
#include <stdio.h> #include <stdlib.h> int main() { int x,t; char command[100]="shutdown -s -t "; char time[100]; printf("输入1:设置定时自动关机 "); printf("输入2:取消自动关机 "); scanf("%d",&x); if(x==1){ printf("请输入关机时间(分钟数):"); scanf("%d",&t); t=t*60;//把分钟数化成秒数 itoa(t,time,10);//把数字转换成字符串,存在time字符数组里 strcat(command,time);//拼接命令 system(command);//调用system函数来执行拼接好的命令 } else if(x==2){ system("shutdown -a");//取消自动关机的dos命令 } system("pause"); return 0; }
这段代码里,我们使用itoa函数,把数字转换为字符串,再是有那个strcat函数进行拼接,最后调用system函数执行命令。一定要深究的话,itoa并不是标准的C语言函数,但大多数编译器里都有它。
我们知道,system函数的返回值是数字,表示执行成功或具体什么错误。那么如果我们想分析它的输出结果,或者用它执行别的C程序,控制输入的内容呢?其实也很简单,就是用DOS命令中的重定向符“< > << >>”,让命令从文件中读取输入信息,或者把显示信息输出到文件。这样我们可以通过操作文件,来具体进行控制了。当年我担任C语言课程助教的时候,就用这个思路写了一个自动评测学生作业代码的程序。
就算这样效率比较低,还是那句话,“有什么关系呢?”我反对让新手一开始就纠结效率和优化的问题,这样会抹杀对编程的兴趣,或者变得不敢写代码。只有通过大量的实践,找到“成功实现一个功能”的成就感,积累足够的信心和经验,才能取得长足的进步。学得深了,再逐步探究更好的办法,我觉得这才是合适的顺序。
3、人机交互
①黑框框(控制台界面)
入门阶段,最受初学者反感的就是那个讨厌的黑框框了,看见它就想起无趣的scanf和printf,感觉相差了整整一个时代……其实吧,就算是黑框框,也能玩出花儿~
01. getch语句
getch语句是一个“无回显的、即时获取用户按键字符”的函数。也就是说,我们按一个按键,它不会显示在屏幕上,也不需要按回车键,就能直接被getch接收到。接收的方法是:
char c; c=getch();
(最前面别忘了#include
这么一个小玩意儿,它能让我们实现很多的功能:游戏按键控制(有时需要结合上文提到的多线程)、菜单选择输入、输入密码的星号功能。此处我们来看看输入密码的函数实现吧:
//输入密码的函数。传入一个字符数组,以及这个字符数组的大小 void getPassword(char password[],int length){ char c; int i=0; do{ c=getch();//用getch来读取用户输入 if(c==' '){//密码里是不能有空格的 continue; } if(c==''){//退格键的处理 if(i==0){ continue; } printf(" "); i--; continue; } if(c==' '){//回车键的处理 break; } if(i>=length-1){//达到最大长度时的处理 continue; } password[i]=c;//存入数组 printf("*");//显示一个星号 i++; }while(c!=' '); password[i]='';//字符串末尾要添加'' }
当我们需要输入密码时,直接调用这个函数就可以了。测试它的主函数此处就不写啦。效果如图(输入的内容自动变成星号,而且可以任意退格,按回车键完成输入)
02. system("cls");
还记得我们刚才说的,用system函数调用DOS命令吗。“cls”是DOS里的“清除控制台屏幕上的已有内容”的命令,可以清除我们已经输出的全部内容。这有什么用呢?
许多人小时候都玩过“连环画”,在一个本子的每一页画上变化的图案,快速翻动每一页,图像就动了起来。
我们也可以通过system("cls");实现简单的“动画”效果,当然了,刷新太快难免出现闪屏的现象,这个没办法,毕竟这就是个土办法……
举个例子,不知道大家有没有听说过“生命游戏”,也就是是英国数学家约翰·何顿·康威在1970年发明的细胞自动机。给个链接,大家去了解一下生命游戏(游戏作品) 我们用C语言来实现它:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> const int type_live=1; const int type_dead=0; const int map_size=20; int map[20][20]; void initGame();//初始化 void run();//每一轮的运行 int getLivingNum(int x, int y);//判断某个格子周边有几个存活的细胞 void show_map();//把地图的状态打印到屏幕上 int main() { initGame(); while(1>0){ run(); show_map(); system("cls"); } system("pause"); return 0; } void initGame(){//初始化 int i,j; srand((unsigned) time(NULL)); for(i=0;i<map_size;i++){ for(j=0;j<map_size;j++){ map[i][j]=rand()%2;//每一个格子的细胞生死状态都是随机的 } } } void run(){//每一轮的运行 int i,j,num; for(i=0;i<map_size;i++){ for(j=0;j<map_size;j++){ num=getLivingNum(i,j); //按规则决定下一轮的生死状态 if(num==3){ map[i][j]=type_live; } else if(num!=2){ map[i][j]=type_dead; } } } } //获取当前格子周边8个格子的活着的细胞数量 int getLivingNum(int x, int y){ int i,j; int num=0; for(i=x-1;i<=x+1;i++){ if(i<0||i>=map_size){//防止数组下标越界 continue; } for(j=y-1;j<=y+1;j++){ if(j<0 || j>=map_size){//防止数组下标越界 continue; } if(map[i][j]==type_live){ num++; } } } if(map[x][y]==type_live){ num--; } return num; } void show_map(){//把地图状态输出到屏幕上 int i,j; for(i=0;i<map_size;i++){ for(j=0;j<map_size;j++){ if(map[i][j]==type_live){ printf(" *"); } else if(map[i][j]==type_dead){ printf(" "); } } printf(" "); } }
这边最关键的界面控制原理,就是用system("cls");不停的清除之前输出的内容,输出一遍,清除一遍,输出一遍,清除一遍……就能让画面动起来了。给个截图,大家自己脑补一下动起来的样子……
03.其它一些小技巧
想让控制台的界面更美观一些,还有两个小方法。一个是system("color xy");控制控制台的背景色和字体颜色(这里的xy,x是背景色,y是前景色,不要直接填xy,而是如下的数值):
0=黑色 1=蓝色 2=绿色 3=湖蓝色 4=红色 5=紫色 6=黄色 7=白色 8=灰色
9=淡蓝色 A=淡绿色 B=淡蓝绿色 C=淡红色 D=淡紫色 E=淡黄色 F=亮白色
另一个是system("title 标题");,能把程序框框左上角显示的标题给替换了。来个简单的例子:
#include <stdio.h> #include <stdlib.h> int main() { system("title hello,world"); system("color B1"); system("pause"); return 0; }
运行效果:
②C语言里的图形库(graphics.h)
C语言也有自己的图形库,我知道的是graphics.h,应该还有别的吧,没研究过。graphics.h好像不是标准库,许多编程软件里都没有,要另外装。我这两天抽空研究研究,来给大家写个例子。graphics.h_百度百科
③图形界面
想要拿C语言实现真正的图形界面程序,那没什么好办法,去学WindowsAPI吧,当年我接触过一阵子,写了几个小东西(就像文章一开始的那个闹钟的截图),但没有深入研究,忘得差不多了,所以现在实在不敢给大家讲太多。而且我觉得吧,WindowsAPI实在不太适合新手去接触,何况根本没这个必要,有时间精力,还不如转而去学Java或者别的更容易做图形界面的语言呢。
4、数据存储
提到数据存储这块,大家第一反应就是“数据库”,想到 SQL 语言,以及眼花缭乱的一个个数据表,好像很麻烦的样子。其实咱们入门阶段不需要这么复杂嘛,完全可以用自定义的文件读写格式来代替。(话说就算是用SQL,也没有想象中那么复杂,这东西是“会用”容易,想“优化好”需要更深的学问)
①文件读写
大家在C语言入门阶段的学习中,大概是学到指针部分的前面或后面一点(不同的教程顺序不一样),就会学到文件读写的基本操作,咱们先简单复习一下:
fopen函数,以某种模式(读、写等等)打开一个文件流fopen_百度百科
fprintf函数,简单理解就是往文件里写入内容的“printf”函数fprintf_百度百科
fscanf函数,简单理解就是从文件里读取内容的“scanf"函数,注意“读字符串时遇到空格或换行结束”fscanf_百度百科
fgets函数,从文件里读字符串,一次读一行,遇到换行结束,遇到空格不结束fgets_百度百科
fclose函数,关闭文件流fclose_百度百科
feof函数,判断文件流是否到结束位置了feof(函数名)
这些函数就是咱们处理数据存储的基本工具~
说白了,数据存储,就是把我们想要保存的数据储存在硬盘上,留着下次(或者每次)使用,不会像那些临时存在内存空间里的变量那样,随着程序的关闭而Say Goodbye。在入门阶段的项目实践中,我们只需要自己规定好数据存储的格式,然后在程序里按照格式读取或写入文件,就OK了。
老规矩,拿例子说话~还记得开篇我做的那个“都市浮生记”吗?它涉及到用户游戏数据存档功能,玩游戏玩到一半,可以存档,然后下次接着玩~我们就来看看这部分功能的实现:
首先,设计一个文件存储结构:
我们来分析,在这个游戏中,玩家重要的临时数据有哪些:
01. 玩家名称Name
02. 当前金钱数额Money
03. 当前仓库容量Capacity
04. 游戏进行的天数Day
05. 库存货物数量Num
06. 这些具体库存货物的信息(货物编号ID,数量N,进货价格M)
07. 由于我这个游戏当时设计的思路,是支持别人更改数据,写扩展包的,所以增加了一个“游戏版本名称Version”的数据存储,位置放在文件开头。
怎么样,是不是有一种做输入输出练习题的既视感。其实这玩意儿改一改,添加一点需求,就可以是一道编程习题了。。。我们先来结合游戏和文件内容看一看效果
游戏天数不一样是因为“存档的时候是第4天,但再次开始游戏时直接进入了下一天”。
这边没有完整显示对应的数据,反正就是这个意思,大家意会一下~
接下来看看代码是怎么实现的(两年前的源码了,不是很规范,我大致加了一下注释,大家领会思路就好)(注意,我项目里用到了bool类型,C本身是没有的,需要引用stdbool.h头文件c语言中
bool READ_USER(char *filename) { int i,n; FILE *fp; fp=fopen(user.filename,"r");//以只读模式打开文件 if(fp==NULL) return false;//文件打开失败…… fgets(user.bagname,100,fp);//读取版本号 user.bagname[strlen(user.bagname)-1]='';/*我忘了当年写这句话是干嘛了,莫非fgets不会自动添加''吗,还是我自作多情?现在有点忘了,大家可以自己测试一下,评论里告诉我。*/ if(strcmp(user.bagname,area[0])!=0)//对比存档的版本和当前游戏版本是否相同 { printf("存档文件与当前扩展数据包不匹配! "); return false;//版本不同,再见吧~ } fgets(user.name,100,fp);//读取玩家名字 user.name[strlen(user.name)-1]='';//同上面那个''的注释 fscanf(fp,"%lld %d %d ",&user.money,&user.storage,&user.day);//读取金钱、仓库容量、游戏天数 fscanf(fp,"%d ",&user.cargo_amount);//读取库存商品数量 user.be_used=0;//忘了是干嘛的了 for(i=0;i<user.cargo_amount;i++)//循环读取每个商品的信息 { fscanf(fp,"%d ",&n);//读取商品id fscanf(fp,"%d %d ",&user.cargo[n].amount,&user.cargo[n].total_price);//读取该商品的数量、价钱 user.be_used=user.be_used+user.cargo[n].amount;//好像是计算已使用的库存容量? } fclose(fp);//关闭文件 WRITE_RECORD();//自己定义的另一个函数,好像是写排行榜来着 return true;//返回true,表示成功读取了存档数据文件 }
然后再看看保存存档(写文件)的那个函数吧:
void WRITE_USER(char *filename) { int i; FILE *fp; fp=fopen(user.filename,"w");//以写的模式打开文件流,如果文件不存在则新建一个。 fprintf(fp,"%s ",user.bagname);//输出游戏版本名称 fprintf(fp,"%s ",user.name);//输出玩家姓名 fprintf(fp,"%lld %d %d ",user.money,user.storage,user.day);//金钱、仓库、天数 fprintf(fp,"%d ",user.cargo_amount);//商品数量 for(i=0;i<goods_amount;i++)//循环输出商品信息 { if(user.cargo[i].amount!=0) fprintf(fp,"%d %d %lld ",i,user.cargo[i].amount,user.cargo[i].total_price); } fclose(fp);//关闭文件流 }
就是这么简单粗暴的办法,自己规定文件结构,用简单的文件读写函数进行操作,就可以实现简单的数据存储功能。我另一个背单词的小软件也是用这个思路处理的,当时还特意写了一个转换程序,把我从百度文库搞下来的单词词库(复制到txt里的),转换成程序需要的格式。
②数据库操作
当然了,这种简单粗暴的方法,不适于大规模的数据存储,因为不方便查询和修改,只能是初学阶段的“权宜之计”(当然了,在实际开发中,小规模数据,尤其是允许用户自行修改的配置文件,也可以用类似的思路去处理)。如果要处理大规模数据,还是规范一点,操作数据库吧。
操作数据库,首先需要学习基本的SQL语法。这个不是很难,理解基本概念,然后照着格式写就行。SQL教程_w3cschool
其次,就要考虑如何与数据库连接。首先你要安装一个数据库,比如MySQL……然后需要学习C语言连接数据库的方法,这块我也没试过(我一般拿Java和 PHP 对接数据库,没试过直接用C写),所以抱歉没法详细介绍。给两个链接大家感受一下吧。c语言连接 mysql 数据库的实现方法_C 语言 , 用C语言操作MySQL数据库,进行连接、插入、修改、删除等操作 。个人认为,在初学阶段的项目实践中,不是非得死磕数据库。最好换个更方便的语言去学数据库,学明白了,真要深入探索,增加效率神马的,再换回C继续深入。
5、网络通信
入门阶段的项目实践中,用到网络通信的情况不多见,实在不建议大家刚上来就挑战CS架构(客户端-服务端的架构)甚至BS架构(浏览器前端-服务端的架构)的项目,要学的东西挺多的。
当然,如果只是想简单实现两个程序的联机通信,学习Socket编程接口,照着网上的样例代码改就可以了。今天本来想试试的,结果发现自己的IDE没有对应的库文件,按网上的方法折腾了一下没有搞定,过两天折腾清楚了再跟大家分享吧。先丢几个链接在这儿,感兴趣的也可以一块试一试。
socket(计算机专业术语)
C语言的Socket编程例子(TCP和UDP)
使用dev-c++做socket编程遇到的问题和解决过程
总之呢还是那句话,我觉得初学者可以暂时不接触C语言的网络通信,想做涉及网络通信的程序,可以转Java、PHP、 Python 之类的语言,更方便一些。然后需要辅以学习计算机网络原理之类的理论基础。初步掌握之后,再想深入底层原理,转回C语言也不迟。
使用C语言图形库写的“吃豆人”小游戏:
关于C语言Socket编程,从网上找的代码,调试通了,这是服务端,客户端没截图:
【责任编辑:庞桂玉 TEL:(010)68476606】
以上所述就是小编给大家介绍的《为什么学完了c语言,我只会写计算机程序?》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
JavaScript语言精粹
Douglas Crockford / 赵泽欣、鄢学鹍 / 电子工业出版社 / 2009-4 / 35.00元
本书通过对JavaScript语言的分析,甄别出好的和坏的特性,从而提取出相对这门语言的整体而言具有更好的可靠性、可读性和可维护性的JavaScript的子集,以便你能用它创建真正可扩展的和高效的代码。 雅虎资深JavaScript架构师Douglas Crockford倾力之作。 向读者介绍如何运用JavaScript创建真正可扩展的和高效的代码。一起来看看 《JavaScript语言精粹》 这本书的介绍吧!