为什么学完了c语言,我只会写计算机程序?

栏目: C · 发布时间: 5年前

内容简介:以前学C语言的时候,写过几个小程序,还算蛮有意思的。先上程序截图,占个坑,然后再慢慢讲做这种小玩意的通用思路。在这里我们不谈软件架构神马的专业知识,就站在入门水平能理解的角度思考,我觉得可以分为5个部分:

为什么学完了c语言,我只会写计算机程序?

以前学 C语言 的时候,写过几个小程序,还算蛮有意思的。先上程序截图,占个坑,然后再慢慢讲做这种小玩意的通用思路。

温馨提示:亮点在最后

1、贪吃蛇:

为什么学完了c语言,我只会写计算机程序?

2、都市浮生记(以前有一个很老的小游戏叫“北京浮生记”,仿那个写的,去各种地方买卖商品):

为什么学完了c语言,我只会写计算机程序?
为什么学完了c语言,我只会写计算机程序?

3、背单词的软件(当年女朋友刚考上英语专业,写给女朋友记单词用的,然而被各种手机APP秒杀了,说实在的,如果不考虑界面的话,我觉得我这个功能还是蛮强大的……)

为什么学完了c语言,我只会写计算机程序?

4、C语言结合WindosAPI实现的图形界面闹钟

为什么学完了c语言,我只会写计算机程序?

首先我们需要知道,一款软件究竟有哪几个部分?

在这里我们不谈软件架构神马的专业知识,就站在入门水平能理解的角度思考,我觉得可以分为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]='';//字符串末尾要添加''  
}  

当我们需要输入密码时,直接调用这个函数就可以了。测试它的主函数此处就不写啦。效果如图(输入的内容自动变成星号,而且可以任意退格,按回车键完成输入)

为什么学完了c语言,我只会写计算机程序?

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");不停的清除之前输出的内容,输出一遍,清除一遍,输出一遍,清除一遍……就能让画面动起来了。给个截图,大家自己脑补一下动起来的样子……

为什么学完了c语言,我只会写计算机程序?

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语言,我只会写计算机程序?

②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”的数据存储,位置放在文件开头。

怎么样,是不是有一种做输入输出练习题的既视感。其实这玩意儿改一改,添加一点需求,就可以是一道编程习题了。。。我们先来结合游戏和文件内容看一看效果

为什么学完了c语言,我只会写计算机程序?

游戏天数不一样是因为“存档的时候是第4天,但再次开始游戏时直接进入了下一天”。

为什么学完了c语言,我只会写计算机程序?

这边没有完整显示对应的数据,反正就是这个意思,大家意会一下~

接下来看看代码是怎么实现的(两年前的源码了,不是很规范,我大致加了一下注释,大家领会思路就好)(注意,我项目里用到了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语言,我只会写计算机程序?

关于C语言Socket编程,从网上找的代码,调试通了,这是服务端,客户端没截图:

为什么学完了c语言,我只会写计算机程序?

【责任编辑:庞桂玉 TEL:(010)68476606】


以上所述就是小编给大家介绍的《为什么学完了c语言,我只会写计算机程序?》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

浅薄

浅薄

[美]尼古拉斯·卡尔 / 刘纯毅 / 中信出版社 / 2015-11 / 49.00 元

互联网时代的飞速发展带来了各行各业效率的提升和生活的便利,但卡尔指出,当我们每天在翻看手机上的社交平台,阅读那些看似有趣和有深度的文章时,在我们尽情享受互联网慷慨施舍的过程中,我们正在渐渐丧失深度阅读和深度思考的能力。 互联网鼓励我们蜻蜓点水般地从多种信息来源中广泛采集碎片化的信息,其伦理规范就是工业主义,这是一套速度至上、效率至上的伦理,也是一套产量最优化、消费最优化的伦理——如此说来,互......一起来看看 《浅薄》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器