1 C语言入门
学习编程之前,我们必须要明白一点:编程的过程,实际上就是人与计算机对话的过程。
就和中文、英文一样,编程语言也是一门语言,只不过与这些自然语言不同,编程语言是一种有限制性的语言而已。
什么是限制性?
那就是一种语义有其固定的规则写法,而自然语言,你却可以任意组合近乎无穷的表达方式。
比如一个动作:吃饭。
那么编程语言的规则可能就只有这一个(或少数几个)形式:吃饭,**你吃饭 **之类的。
而自然语言,你却可以随便说,只要别人能理解你的意思就行了,比如:吃中饭!,吃晚饭 之类的。
1.1 C语言是什么?
正如自然语言有中文、英文、日语等等。
编程语言也有很多:C、C++、python、java、go、rust……
而C语言就是其中一门非常古老的语言。
虽然古老,其直到今天依旧生命力顽强,所以对于新人来说,我依旧建议你将它作为第一门编程语言进行学习。
因为它几乎是现代互联网的基础,你如今所能看到的一切:电脑、手机、智能手表、汽车……都有它的影子。
原因无它,就是因为它接近底层,效率足够高。
一般来说,越靠近硬件的就越底层,执行效率就越高。
它可以让你肆无忌惮、随心所欲的操控你想操控的一切,而代价就是,编程过程中充满着凶险,开发效率缓慢等。
但无论如何,我认为学习它都是有必要的,因为它深入底层的这个特性,你才能透过它真正理解一切。
学会了它,以后你再去学习其它更高级的语言,只会觉得轻松~
嗯……可能还得学习C的大哥C++之后才会有这种感觉,可以参考本站的其它C++系列教程。
因为如今的高级语言,大多数都是在C(或C++)上开发而来的。
1.2 如何学习?
我并不希望从0开始,硬给你讲述一些固有的规则,那实在是太无聊的,因为这些内容你在互联网上已经可以随处可见、任何不懂的知识点都可以询问ChatGPT、DeepSeek等AI。
为了能够让你感受到编程是一件很酷的事情,本文会以项目为主导带大家学习。
也就是说,我会从一个有趣的项目开始,从0带着大家开发,而从这个过程当中,我们一定会遇到很多难点、问题。
之后,我们就会想办法去解决它,从中学习知识。
死板的知识是无趣的,只有将学到的东西应用到实践中,从实践中学习到知识,才能让学习变得有趣!
2 项目介绍
为了让学习C语言的过程没那么无聊,本文会以一个经典游戏推箱子为主导来学习相关知识点。
这个游戏非常的经典有趣,代码量也算是中规中矩,最终大概有三百行左右的样子,再加上其中可以运用到很多各个编程方面的知识,拿来练手最合适不过了!
最终完成后呈现出来的效果如下:
当然,如果你稍微了解过一点C语言,都是知道C语言本身是写不出这样好看的游戏界面的。
对,C语言本身确实写不出来,但它可以调用其它第三方库啊!
因此这个项目涉及到的知识点还是很多的。
基本的C语言语法就不说了,那肯定是会用到的。
不敢说全部用到,但一定会用到其中相当一部分知识点。至于没有用到的知识点,也不用慌,可以再去进阶学习本站《C/C++实战入门到精通》等系列文章。
除此之外还有:文件操作、GUI编程、第三方库的使用等等。
3 了解软件
无论要做什么,首先从一个较为宏观的角度去了解它都是一件非常有必要的事情。
不过初学者却很难站到一个更高的角度来审视,而这也就是本教程所存在的意义了。
本教程最终实现的目标是:推箱子小游戏。那么第一个问题就是,什么是游戏?
3.1 理解游戏
广义上来说,游戏当然不仅仅是指电脑、手机游戏,比如小时候玩过的捉迷藏,当然也可以叫做游戏。
但由于本教程是教编程相关的,所以这里指代的游戏,就可以狭义的称其为游戏软件。
游戏软件,与你手机、电脑上用的其它软件,比如QQ、微信、支付宝等,并没有什么区别,都是软件。
它们都是运行在电脑、手机上的一个个程序而已,只不过游戏更侧重于娱乐,而其它软件则更侧重于实用,仅此而已。
3.2 什么是程序?
上面我提到了一个词:程序。
广义上讲,就是规定的一系列步骤,比如现实世界中的司法程序。
而狭义上来说,计算机所运行的一系列指令组合起来,实现某一个功能,便可称其为程序。
也许你已经听到过无数遍了,计算机的本质其实就是二进制的0和1。
无论是你看的视频、听的音乐、玩的游戏,在计算机眼中都是二进制的0和1,除此之外,计算机就什么都不认识了。
那计算机为什么能仅仅依靠0和1两个数字就能展现出我们所能看到的如何繁荣的互联网呢?
答案就是数量。
虽然一位数字只能表达两个信号:0或1,即 。
但如果是两位,那就能表达四个信号:00、11、01、10,即:。
而如果是8位呢?那就能表达 个信号。
这样能表达的含义不就丰富起来了吗!
比如用 00001111
这个8位的二进制数字信号,人为的规定其是做乘法运算的,00001100
这个8位的二进制数字信号,人为的规定其是做加法运算的。
那么计算机便能只在认识0和1这两个数字的基础上,完成超越二进制的加法以及乘法运算了!
而这就是早期的机器语言,即二进制编程,面向机器编程。
这样的机器指令可能有数百个之多,人为记忆起来是很困难的。
那我们就可以用我们所知道的符号来代替它呀!
比如:用符号mul
代替 00001111
这个数字,用符号add代替00001100 等。
这样我们写代码的时候,就不用写这些二进制数字了,而是可以直接用这些符号来写了。
写完之后呢,再通过一个提前写好的程序,来将这些符号重新转化为对应的二进制指令数字,交给计算机执行不就行了?
第一个将符号转换为二进制数字的程序肯定是用机器码写的,其开发的痛苦程度都不用多说了!
这便是汇编语言的由来,它的每一个符号都对应着一段二进制机器指令。
但汇编指令写起来仍然很痛苦啊!
举个例子,以下就是用汇编语言打印 hello world 的代码:
DATA SEGMENT
BUF DB 'HELLO WORLD!$'
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA
START:
MOV AX,DATA
MOV DS,AX
LEA DX,BUF
MOV AH,09H
INT 21H
MOV AH,4CH
INT 21H
CODE ENDS
END START
写好上面这串代码后,我们还需要找到对应的汇编程序将其翻译为二进制后,才能让计算机来执行。
是不是只是看着就很绝望?
为了简化程序员的负担,后面就出现了更加高级的语言,也就是我们本系列文章要讲解的C语言:
#include<stdio.h>
int main(){
print("hello world");
return 0;
}
同样是输出一个hello world,是不是比汇编要简单多了?
不过其原理同样不复杂,就是在汇编的基础上,又添加了一层而已。
当你写好了这段C语言代码,通过对应的C语言编译器,就会将其翻译为汇编代码,最终再通过汇编程序将其翻译为计算机能够理解的机器码,也就是二进制数字。
虽然这样做可能会损失一部分性能,但如今的编译器非常强大,与为我们提供的开发速度来说,这点损耗已经完全算不上什么了。
甚至可以说,就算你手写汇编,也不一定比用C语言编译器编译后的汇编性能更好。
而第一个C语言编译器程序不出意外的话,自然也就是用汇编语言写的了。
但后面的C语言编译器,便是用C语言本身写的了,毕竟开发效率提升太多了!
语言本身写自己的编译器,称为自举,并不是任何语言都有这种能力。
以上,当我们用C语言写好一段代码后,程序的执行流程:
- 将C语言代码翻译会汇编代码
- 将汇编代码翻译为机器码
- 计算机执行机器码
而作为程序员来说,程序,常常就是指代的我们所写的代码。
对于本系列来说,也就是C语言代码。
通过写代码,我们就能够让计算机做一些我们想让它做的事情。
4 认识VS
前面可能有点无聊,因为都是一些比较空洞的概念。
但总体来说,你只需要知道,当我们写好C语言代码后,是需要依靠一个叫做编译器的东西将其翻译为二进制的,就可以了。
而本节,就是来介绍这个编译器的。
4.1 编译器认识
编译器并不是只有一个,目前主流的操作系统主要分为两类:window系统、类unix系统。
其中window系统一般用的都是vc++编译器,而类unix等系统上用的则是GCC、Clang等编译器。
但比较烦人的是,这些不同的编译器实现的规格并不完全相同。
所有很多时候同一份C语言代码在某个编译器下能正常运行,可能在另一个编译器下就会报错。
因此在写C语言代码的时候,除了尽量用通用、标准的写法,也最好是提前知道自己的代码最终要使用哪个编译器上,就直接用该编译器进行开发。
当然,这种情况也是极少数的,大部分的C语言代码,在各个编译器中都是统一、可正常运行的。
4.2 安装编译器
那么首先我们第一步,就是来安装编译器了。
就目前来说,PC端的游戏一般都是运行在windows系统之上,大部分软件,第一适配的系统也必然是windows系统。
并且大部分新人使用的也应该是windows系统, 加上我个人比较熟悉windows系统。
因此本系列教程将以windows系统上的vc++编译器作为本系列教程所使用的编译器。
相比于其它编译器,vc++编译器是其中做的最好的(好在其有一个强大无比的集成开发环境),因此新人用来入门应该也是最容易的。
官网地址为:Visual Studio
点击跳转进入官网后,下载最新版的Visual Studio Community版本即可:
安装过程很简单,就一路点击即可,中间选项任意,唯一需要注意的点是下面这一步:
这个软件为集成开发环境(Integrated Development Environment,简称IDE)。
它不是编译器,但它包含了编译器,它是一个极其强大的综合体,这一点我们以后会体会到的。
这可以从它的官网介绍中看出一二:
而这里勾选的就是编译C语言代码的组件,你可以从它的内容中看到:MSVC、CLang等字眼。
其中MSVC便是前面提到的vc++编译器,Clang就是前面提到的clang编译器。
其默认使用的是msvc编译器,这个直接默认安装即可,不用理会。
如果你C盘容量不够,可以更改安装位置,我这里是直接安装到C盘的:
选择完成后,右下角有个安装按钮,点击安装,等待即可。
完成后,你就能看到一个类似下面这样的界面:
然后通过创建项目,我们就能够开始写代码了!
但为了让你能够更好的理解项目与我们所写的代码之间的关系,会在这里先暂缓,下文来介绍一下VS(Visual Studio的简称)中的各种逻辑、组织之间的关系。
5 hello world
有了编译器,我们就可以正式开始写代码了,而学习编程语言一个传统便是,第一步先尝试输出hello world。
为了输出hello world,我们就需要写程序。
由于VS是以项目为基本运行单位的,所以我们首先就需要创建一个项目:
项目也是有很多类型的,所以VS提前给我们准备好了很多可以直接用的项目模板。
但既然我们是学习,那自然还是从0开始了,即:选择空项目
最上方的红框可以用来筛选项目模板,选择好我们这里要创建的空项目,就可以点击下一步了。
然后在下一个配置页面,我们就用上了上一章介绍的知识点:
位置不说了,就是存放这个项目的文件夹,可以自行选择。
其中项目名称、解决方案名称,就是前文提到的项目、解决方案。
解决方案是包含项目的,所以一般来说,最下方那个:将解决方案和项目放在同一目录中,最好不要勾选。
除非你觉得你这个解决方案只会有这一个项目,那勾选了倒也没啥。
两者可以分别命名,我这里就直接默认了,然后继续下一步,点击右下角的创建即可。
VS强大而复杂,所以布局很多,但不要慌,其它的暂时都不用管,首先第一步就是找到解决方案资源管理器(一般在右边且默认打开):
如果你的界面没有这个,可以从左上角的视图菜单中调出来:
现在解决方案里面就只有一个项目,而这个项目还是空的,所以我们还需要为其创建一个源文件,用来写代码:
最简单的方式就是点击这个源文件目录,然后按快捷键:Ctrl+Shift+A
:
注意:前面的名字无所谓,但要将其默认后缀.cpp
改为.c
因为实际上这个VC++编译器是C++语言的编译器,但C++完全兼容C语言,所以可以直接用,但为了让其能够识别出这是纯粹的C语言代码,就需要改一下这个后缀名。
这个快捷键哪来的?你可以直接右键这个目录也能一路找到添加文件的选项:
然后,你就可以在这个文件中写下这段代码:
#include<stdio.h>
int main() {
printf("hello world");
}
样式如下:
写好后,点击上方的绿水箭头,就能编译运行了:
结果:
是不是非常方便!
在这个IDE中,我们只需要按下一个按钮就可以直接将代码编译完成之后,直接运行!
但还是要记住,它依旧执行了两个步骤:先编译,再运行。
那么编译后的二进制可执行文件呢?
它就存放在这个项目文件夹中,你也可以从上面的输出中看到它的位置:
在文件浏览器中也能找到:
但你现在直接点击运行它是看不出效果的,你大概只能看到一个黑色窗口一闪而逝。
这是因为我们的代码目前只输出了一个hello world就结束了,程序结束了,窗口就自然被销毁了。
后面我们会了解到如何让这个窗口保留下来。
6 了解内存
虽然可能有点啰嗦,但我发现有些新人即使已经写了几个月的代码了,却依旧不知道代码是怎么运行的。
所以还是决定稍微啰嗦一下。
因为受早期很多手机厂商宣传的影响,很多人都会把内存当作设备的存储空间大小。
但实际上,至少在编程领域,内存一般都指的是计算机的运行内存。
以前面编译生成的那个存放在文件夹中的二进制可执行文件(.exe)为例,它存放的位置是在硬盘中,而不是在内存中。
一般C盘、D盘的大小,指的是硬盘大小,用于长期存放数据的,即使你电脑关机,之后打开电脑,硬盘上的数据依旧存在。
而内存,则必须要时时刻刻通电才能保存数据,只要你关机,那么数据就会立刻丢失。
而想要运行一个软件,就必须要将这个软件从硬盘加载到内存中,才能运行。
因为离CPU近等等原因,内存存取数据的速度比硬盘要快得多。
所以,当你双击一个应用程序时,实际上其本质就是在将这个可执行文件的数据,加载到内存上去,这样计算机才能够运行它。
那么如何才能看到内存呢?
这可以从windows系统自带的任务管理器中查看。
快捷键为:win+X
,其中win是键盘上画着窗口图案的那个键。一般在alt键旁边。
然后你就能从调出来的菜单中看到这个任务管理器,打开它即可:
然后就能在性能页面,看到内存:
比如我这里,就是32G的内存,已经用了14.8G了。
后面编程过程中,我们所有分配的空间,实际上都是在分配这个内存。
计算机上所有的应用程序使用的内存,都是在共用这个内存。
当这个内存占用量过大时,电脑就容易出现卡顿,此时你就可以选择去关闭一些占用内存大的应用来解决。
7 C语言基本语法
前面讲了这么多,终于来到了我们的正题:编程。
但光讨论编程细节、语法,是很没意思的,因为这种东西网上随便搜一搜都有一大堆。
所以本文会结合游戏推箱子,来探讨我们实现它具体需要哪些东西,而C语言又有哪些语法刚好可以使用。
如果你真的零基础,那么最好在我提到一个知识点后,就尽量将其理解透彻,比如去浏览器搜一搜相关的关键字、问问AI大模型,否则后面你可能难以跟下去。
7.1 基本代码结构
但在正式分析游戏前,了解一下C语言的基本结构是很有必要的,不然分析起来就会有种空中楼阁的感觉。
正如前面所提到的,一个C语言输出hello world,就需要以下几行代码:
#include<stdio.h>
int main() {
printf("hello world");
return 0;
}
如果我们不需要输出字符串,那么上面的结构还可以进行简化:
int main() {
return 0;
}
也就是说,一个最简单的C语言程序,就是上面这个形式,这是每个C语言程序所必须的。
这被称为主函数,又称main
函数,因为它的名字是main
。
函数的内容我们后面再提,因为它还有点小复杂,目前你只需要记住这个格式即可。
其中的return 0;
,这条语句的作用是返回这个函数的结果,在VS中就算你不写也不会报错,因为编译器会自动帮我们添加上。
并且我们需要知道的是,当将上面这份代码编译执行后,计算机就会从这个main
函数的内部开始,遇到一句,执行一句。
C语言的每条语句都是用英文分号;
作为结束,这就和中文的文章中,用句号作为一句话结束是一个道理。
有了这个结构,那么编译器就已经能够将其编译运行了。
但如果我们想要让它做点事,那就需要写语句了,即代码。
7.2 分析游戏逻辑
将推箱子游戏抽象一下,其实就是一个二维表格,
用数字1,代表墙壁,数字5,代表箱子,数字2,代表金蛋,而数字9,代表我们的人物角色。
那么我们的规则就有了:
- 数字9代表人物,可以在表格中移动,但不能越过墙壁1,且可以推动箱子5。
- 游戏的目标就是将箱子5需要推到金蛋2的位置。
只要我们把这个简单的游戏逻辑写好,之后再将这些数字替换为图片不就行了吗?
所以首先我们的第一个问题就是,如何表示这些数字,并让其可以移动。
7.3 二维数组、数据类型、变量
想要在C代码中表达一个二维表格很简单,可以像下面这样写:
int map[10][10];
这就是一个10*10
的二维数组,你可以将其想象为上面看到的二维表格,并且我们还给它取了一个名字为map
,即地图的意思。
这个名字称为变量名,它有一定的规则,这个可以自行网上查询,比如:C语言变量名命名规则。
规则并不难,简单来说就是,可以用全字母组合(a-z,A-Z)的任意变量名,或者还可以添加下划线_
,又或者添加数字(0-9),但数字不能作为变量名的开头。
而前面的int
,标准说法就是数据类型,意思就是这个二维数组里面将用来存放整数。
int,即为整数单词integer的缩写,这是C语言的规定,不必想太多,记住即可。
C语言中的数据类型有很多,但本系列只会用到几个基本的数据类型,为了节约篇幅,可能不会细讲,具体内容可参考本站的其它文章。
再次提示,最后是用的英文分号作为结束的,英文分号,英文!很多新手写代码,就会敲上去中文分号,一运行就报错!
结合前一章的内容,这句话的作用就是,在内存中分配一块可以存放10*10
大小的整数二维数组内存空间,用于后面我们使用。
7.4 循环
只是有这样一个二维数组可能还不够,因为这块内存是系统分配给我们的,那这块内存以前存放的什么数据,我们怎么知道呢?
前面说过,内存是电脑所有应用共享的,而你得到的这块内存,很有可能是刚被其它应用程序使用完后,就马上交给你用的,此时这块内存上的数据完全未知。
所以一般来说,我们第一步都会先将其清0,
用最笨的方式,就是一个一个的去赋值,就像这样:
map[0][0]=0;
map[0][1]=0;
10*10=100
,所以你需要这样写100次,才能将其全部赋值为0。
数字0的含义前面没有提,我们可以认为将其赋予为空,即作为游戏草地背景。
二维数组的使用方式就是像上面那样,第一个[]
中写你想要访问第行,第二个[]
中写你想要访问第几列。
无论行还是列,都是需要从0开始计数,也就是说,长度为10代表0-9可用。
那么这个时候,我们就需要用到循环了,它可以简化这种重复的步骤:
for(int i=0;i<10;i++){
for(int j=0;j<10;j++){
map[i][j]=0;
}
}
循环对于新人来说,可能也有点复杂,但不要紧,我们可以从里到外刨析。
上面的代码完成了一件事情:将map这个二维数组全部清零了。
并且我们并没有写一百次map,而是只写了一次map[i][j]=0
。
i代表二维数组的第几行,j代表二维数组的第几列,它们也叫变量:可变的量。
显然,这两个数字要分别能从0变化到9,这个赋值操作才能完成,而这就是循环的目的。
一个最简单的循环写法为:
for(;;){
}
也就是用单词for
来写循环,后面紧跟小括号(),然后是大括号{}。
其中,小括号中还有两个分号;
,将其分为了三个区:
- 第一个区:用于初始化变量,比如上面的
int i=0
,int j=0
,这就是在初始化两个int类型的变量,名字为i和j的,初始值为0。 - 第二个区:用于判断是否继续循环,比如上面两个都是i<10,j<10,即,只有这两个变量小于10,这个循环才会持续下去,否则,这个循环就结束了。
- 第三个区:用于存放每次循环执行的代码,比如这里就是,每次循环,就会执行
i++
,j++
,它们等价于i=i+1,j=j+1,即自身加1,每次循环都会执行一次。
而后面的大括号,就是每次循环要执行的内容了。
因此,一个for循环:
for(int i=0;i<10;i++){
}
意思就是执行十次后面的大括号中的内容,而每次执行,i都会自增1,这个特性就可以用来作为二维数组的行。
然后在循环中再放置一个循环,也就是二重循环:
for(int i=0;i<10;i++){
for(int j=0;j<10;j++){
}
}
由于第二个循环在第一个循环的大括号中,所以其达成的效果就很明显了。
外层的i每次循环一次,内部的j就循环10次(0-9)。
由于外层i一共要循环10次(0-9),因此内部j就循环的100次,分十次循环(0-9),这不就正好和二维数组对应上了吗?
第一行有10列,第二行也有10列,以此类推,共100个。
这个如果想不明白的,最好再去网上搜一搜、问问AI,理解明白再往下看,对于游戏来说,数组很重要。
只不过因为这个游戏是二维的,所以这里介绍的是二维数组,其实还有更常用的一维数组,也就是只有一个[]
符号的。
同样的,循环也是,一般一重循环用的最多,但由于这里是二维数组,所以就用的更难的双重循环。
完整的代码如下:
int main(){
int map[10][10];
for(int i=0;i<10;i++){
for(int j=0;j<10;j++){
map[i][j]=0;
}
}
}
8 让游戏一直运行
前面我们通过数组、循环,就完成了游戏地图的建立:二维数组所对应的游戏地图。
但那显然还是不够的,所以本节继续在此基础之上,去完善它。
前面我们已经初始化好的游戏地图数据对吧!
但你会发现,程序一直都是从main函数内部开始,看到一句,就执行一句,执行结束后,程序就结束了!
可我们看到别人的游戏、软件怎么不是执行一会就自己结束呢?
难道是他们写的代码数量超级超级多。让系统都执行不完吗?
显然不是的,毕竟以如今计算机的运行速度,你就算写上十年的代码,估计也能很快给你跑完(可能只需要花费几十秒乃至几秒)。
而这就用到了上一章提到的循环了,它可以让同一块代码反复的执行,只要让它一直循环起来不就行了吗?
死循环的方式很简单,只要你不写for循环中三个区域中的中间那个判断条件区域,它就是死循环。
所谓死循环,意思就是一直循环执行代码,没有跳出循环的条件、可能性,就像这样:
for(;;){
}
只要什么都不写,那它就是个死循环,会一直执行后面大括号中的代码。
前面提到过,当我们找到编译后的可执行文件时,直接点击它运行只能看到它一闪而过,解决方式也很简单,在最后面添加一个死循环不就行了嘛!这样程序就永远不会执行结束了!
这样写着可能有的人觉得不太好看,所以也有另一个循环可以使用:
while(true){
}
while循环后面的小括号内,就只有一个判断条件,我这里直接让这个判断条件为true,那它自然就会永远执行下去!
它就相当于只剩下第二个判断区域的for循环,看自己喜好,用哪个都行,作用都是一样的。
8.1 判断语句
但你又发现了,如果写死循环,那里面的代码不就千篇一律了吗?
比如前面用到过的输出hello world的代码,如果将其放在死循环里面,那就会一直输出hello world,这有什么用?
所以这个时候就需要判断语句了,它可以让你即使是在同一段代码中,也能在不同的情况下使用不同的代码片段。
#include<stdio.h>
int main() {
for (int i = 0;;i++) {
if (i == 0) {
printf("i==0");
}
else {
printf("i!=0");
}
}
}
比如上面这段代码,由于for循环我没有写第二个判断区域,所以它就是一个死循环。
但我写了第一个区域,声明了一个整数int类型的i变量,并将其初始化为了值0。
并且每次循环执行结束后,都会执行一次i++
操作,等价于i=i+1,即自增1。
然后我就在代码中写上了一个判断语句,就判断这个变量i是不是等于0的,根据这个条件,我就可以去执行不同的代码。
判断语句用到的关键字就是if
,其后紧跟的小括号内填的是判断条件,这个和while语句后面的小括号功能是一样的。
唯一不同的是,if判断语句只判断一次,而while判断语句会一直循环的进行判断。
C语言中,乃至绝大部分编程语言中,一个等于(=)是赋值操作,意思是将右边的值赋值给左边,而两个等于=才是判断两边是否相等,而不等于则是用感叹号加等号表示(!=)
如果判断为真,那就会执行其后的大括号中的语句。
至于后面的else
关键字,相当于是if判断语句的扩展,也就是分支,如果其判断语句为false,那就会执行后面else紧跟的大括号中的代码了。
if(){}
else{}
也正是因为其有这个特性,才具有根据不同情况去执行不同代码的能力。
但单一判断条件,在很多情况下依旧不够,还需要更多的判断条件,那就可以再在if
与else
中间添加else if
语句:
if (i == 0) {
printf("i==0");
}
else if (i == 1) {
printf("i==1");
}
else if (i == 2) {
printf("i==2");
}
else {
printf("i!=0 且 i!=1 且 i!=2");
}
else if
和if
语句本身使用起来基本是一样的。
其调用逻辑仍然是从上到下,挨个判断,只要有一个判断条件为真,那就不会再判断以及执行后面的了。
else if
可以中间无限添加,而这就能赋予程序无穷的功能。
9 接收键盘输入
但显然,这仍然是不够的,毕竟现在的判断条件是我们自己写的,而游戏明明是玩家通过按键盘来执行对应的步骤啊!
对的!判断是基础,用户是可以输入很多按键的,所以我们就需要判断用户每一次输入的是什么内容,这样我们才能根据不同情况,让游戏做出不同的反应!
首先需要说明的是,用户输入的形式是可以多种多样的。
比如最常见的就是鼠标、键盘、屏幕滑动点击。
除此之外,游戏手柄、VR设备等,同样可以作为用户输入相关信号的通道。
而这些一般都统称为输入设备,本文要讲的就是输入设备中的一种:键盘。
9.1 库
但在正式介绍如何接受键盘输入的内容前,我们有必要来了解一下:库。
作为当代程序员来说,从某种程度上来看,我们是幸运的,因为很多东西都已经有前人为我们做好了。
也就是说,大多数情况下,我们都不必从零开始,自己去开发一个功能,而是可以直接使用别人写好的东西。
在编程语言中,称这种别人写好的代码为:包、库、箱等等称呼。
但名字不重要,你只需要知道其本质都是一个意思:使用别人写好的代码集合。
库的质量是良莠不齐的,新人写的库一般情况下都没有老手写的库好用。
为了保证库的质量,让程序员可以放心使用,就出现了一个叫做标准库的东西。
标准库,就是设计、规范该语言的官方团队所写的库,无论是质量,还是性能,都有保障,我们可以直接在代码中使用它们。
比如前面我们用到的打印函数:printf,这就是标准库中的东西,是官方提供的。
而它所在的地方,就是一个叫做stdio.h
的头文件中,所以就有了这种代码:
#include<stdio.h>
int main() {
printf("hello world");
}
前面的指令:#include<>
,意思就是包含,即:将这个文件中的代码包含到当前文件中的这个位置。
而这个stdio.h
中就实现了这个printf
函数,因此你才可以在这里直接使用,并打印出字符串到控制台。
如果没有官方提供的这个函数,你就需要自己去寻找不同系统的底层函数说明文档,才能实现这个功能。
stdio.h
这个名字也是有一定含义的,前面的std
即Standard
,标准的意思,后面的io
即:input/output
两个单词的首字母,即输入输出,最后的后缀.h
,意为这个文件是C语言的头文件(header)。
所以一切关于输入输出相关功能的函数,基本都可以在这个文件中找到。
而这里的printf
,就是输出功能的函数。而我们想要接受键盘输入的函数,同样也在这个库里面,这个后面马上就会提到。
9.2 函数
前文中我提到了很多次函数这个名词,在数学定义中,函数一般被写为下面这种形式:
y=2x+5
而更高级一点的写法,则是这种:
y=f(x)
f(x)=2x+5
这虽然只是做了一个替换,但实际上其中的思想却是很值得学习的。
比如很火的科幻作品三体,里面有三个太阳,我想要预测其行动轨迹。
虽然这被证明是不可预测的,但假设真的可以预测,其中的运算过程复杂度都是我们无法想象的。
而作为普通人来说,或者对于大人物来说,他们会在意中间的运算过程吗?不!他们只看重运算结果!
也就是这里的抽象化:y=f(x);
我想要结果y
:太阳的运行轨迹,至于这个y是怎么算出来的,大部分人其实根本不关心,所以将其抽象为一个f(x)
这样一个形式,而这里的x
就是这个函数参数。
也就是要算出这个结果,它需要什么东西?比如要十年前的太阳位置、百年前的天文数据等等。
将这个思想放在代码中也是一样的,比如我们就想要将字符串打印到控制台上,至于里面是怎么实现的,大部分人根本就不关心。
控制台,也称黑窗口、终端等等,就是当你运行程序后弹出来的那个黑色窗口,没有任何图案,只能显示字符的窗口。
这便是函数的意义:封装。
在C语言中,写一个函数就和你写main
函数是一样的:
//函数实现
int fun1() {
return 0;
}
int main() {
fun1(); //调用这个函数
}
但要注意,不同函数在C语言中是不能重名的!
函数分为实现与调用,调用很简单,就是写好函数名,后面紧跟的下括号中填入这个函数需要的参数就行了。
而实现就稍微复杂一些了,主要分为:返回类型、函数名、函数参数、函数体这四大部分。
返回类型就是这里首先写的int
,即整数,普通函数一般编译器就不会像main函数那样,为你填充返回值了,所以你需要自己写return
将这个函数的结果返回回去。
而函数名,主要就是记住不能重名,其它的和变量名规则一样的。
函数参数填入小括号中,只不过这里我们没写。
函数体就是后面大括号中的内容,就是当这个函数被调用的时候,具体要执行的代码。
比较重要两点是:
- 无论你写多少个函数,程序运行后都是从main函数开始运行的,遇到一句代码,就执行一句代码,遇到一个函数,就跳转到这个函数内部去执行,执行结束后再跳回来继续向后执行。
- 函数的实现(或者声明)要在调用之前。
函数真要讲起来,细节非常多,比如这里的函数声明、函数实现又是一个知识点。
但为了节约篇幅,所以这里点到为止,后面用到了咱再继续讲,你也可以自己去网上搜索相关的资料进行学习。
9.3 键盘输入
对于大多数新入C语言的同学,使用的都是scanf
函数来接受键盘输入。
但在VS中直接使用这个函数,是会报错的,因为它不安全。
至于其原因,涉及的比较底层,这里先不深究。并且要说明的是,这是编译器的原因,其它编译器并不会报这个错误。
解决办法有两个:
- 在代码最前面写上:
#define _CRT_SECURE_NO_WARNINGS
- 使用安全的函数:
scanf_s
这个函数就可以用来接收键盘的输入,比如我想写一个接收w、s、a、d这四个按键的,作用是控制游戏角色方向。
那就可以这样写:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main() {
for (;;) {
char d;
scanf("%c", &d);
if (d == 'w') {
printf("按下了上键\n");
}
else if (d == 's') {
printf("按下了下键\n");
}
else if (d == 'a') {
printf("按下了左键\n");
}
else if (d == 'd') {
printf("按下了右键\n");
}
}
}
除却这四个键之外的按键我们不想管,所以这里没写else
语句。
scanf
函数可以接收很多个参数,多个参数用英文逗号隔开,其中最重要的是第一个参数:格式控制参数。
它是一个字符串,用于表示我们想要接收哪种格式的输入,这里我们想要接收的是按键字母,那就是char类型。
与int整数类型一样,char字符类型也是一种数据类型,用于存储一个字符。
比如这里填入的%c
就是格式控制,告诉这个函数我们想要接受的是字符类型,如果是整数类型那就是%d
,这个可以自己用到的时候去浏览器中查即可(或者问chatgpt)。
然后当程序运行到这个函数时,就会卡在这里,等待用户输入(要按下Enter键该函数才会开始接收)。
接收到的内容就会填入后面的参数中,比如这里的char d;
在接收用户输入时,需要在变量名前面添加一个&
,为取址符号,意为拿到对应的变量在内存中的地址,然后将接收到的内容填入进去,新手很容易在这里栽跟头:不加&
符号!
效果如下:
这样确实可以做到接收用户输入,并按情况执行不同的代码了。
但很明显,这样一卡一卡的,每次都需要按下Enter键才能开始接收的效果,太差了!
遗憾的是,标准库中提供的函数功能很有限,不存在那种可以直接接收按键的函数。
好吧……非要细究的化,其实也是有的,只不过很不好用,因此我用的是第三方库提供的函数。
所谓第三方库,就是除却标准库外、由其它程序员开发的库,其中不乏比标准库还优秀的存在。
现在我们的目的是:完成整个游戏的基本框架逻辑!
10 游戏地图绘制
有了前面的基础,虽然你可能觉得到目前为止你学到的东西并不多,但其实这就已经足够让你完成一个简化版本的推箱子小游戏了。
本节将完成游戏的地图绘制。
完成了基础版本的之后,再根据这个版本的不足之处,后面学习其它的新知识点,来将它逐步完善!
10.1 输出
前面我们其实就用过了输出函数,也就是printf
函数。
别看它是入门级的函数,但其复杂度却丝毫不弱,最简单的用法就是打印一个字符串:
printf("hello world");
什么是字符串呢?这同样也是一个数据类型,而且是各种数据类型中最复杂的一个。
所以这里并不打算对它进行深入的解释,对于使用来说,其实只需要知道其最简单的用法就可以了。
显然,你应该已经知道了它的用法,那就是用过两个英文引号包括起来,中间的那一部分就是字符串,可以用来输出的控制台上。
但其实际上的用法还不止于此,因为它还可以用来格式化字符串,比如,我想要按格式,打印年月日:
printf("2023");
printf("-");
printf("09");
printf("-");
printf("01");
上面的输入结果为:
2023-09-01
这显然有点复杂,不是吗?
所以就可以用格式化的写法,等同于下面一句话: