点击链接回顾前几篇:
(一)标准输出cout——一条安静的蛇
(二)代码详解和Sleep()——蛇之闪现
(三)SetConsoleCursorPosition光标移动效果——一条前进的蛇
(四)预定义和函数调用——妄图得分的蛇
在前几篇中,我们学习了如何用cout()输出字符串到屏幕,如何用Sleep()实现闪现,如何用SetConsoleCursorPosition()让蛇前进,以及关于预处理命令#define和函数的相关知识。
接下来,我们将学习在循环中快捷地打印蛇身图案。
for (int i = 0;i < 5;i++) {
SetConsoleCursorPosition(handle, { i,i });
cout << BodySymbol;
}
for是C++关键字,用来定义一个循环。
可以看到,for的()中有三个表达式,用分号隔开,我们称之为表达式一,表达式二,表达式三。大括号{}内的部分叫循环体。
如图:
for (语句一;语句二;语句三){
循环体
}
我们知道,程序一般是顺序执行的,那么for循环是如何执行的呢?
可以看到,当程序运行到for循环时,先执行语句一(预处理语句),然后判断语句二是否成立(非0即为成立),若成立,进入循环,循环体执行一遍过后执行语句三(通常是控制条件语句),然后再判断语句二是否成立,若成立者继续循环,若不成立则结束循环。
也就是说,每次循环在判断语句二为成立后开始,在语句三执行完毕后结束。
程序按照语句二——循环体——语句三的顺序多次执行,直到语句二不成立,循环结束。而语句一仅执行一次。
现在我们可以直观地了解上面那个for循环的意义:
注:i++相当于i=i+1
可以想到:当i依次是0,1,2,3,4时,在屏幕上{0,0} {1,1} {2,2} {3,3} {4,4},五个点处依次打印出了BodySymbol,蛇的身体。
在主函数中写入这个for循环:
int main() {
handle = GetStdHandle(STD_OUTPUT_HANDLE);
for (int i = 0;i < 5;i++) {
SetConsoleCursorPosition(handle, { i,i });
cout << BodySymbol;
}
getch();
return 0;
}
编译运行:
注:getch()函数等待一个字符输入(敲键盘),可以让程序暂停下来,需要#include<conio.h>
接下来:改LENGTH 的值为5
#define LENGTH 5
将for循环移入ready()函数,并做一些修改
void ready() {
for (int i = 0;i < LENGTH;i++) {
SetConsoleCursorPosition(handle, { i,10});
cout << BodySymbol;
}
cout << HeadSymbol;//最后打印一个蛇头
}
我们将y坐标改成“恒为10”,使得输出将在从左到右的一行(第十行)
注:之所以不设为0是为了体现SetConsoleCursorPosition()的作用,因为光标本身就在第0行(Y=0),从左向右移动(x++)
将5改为LENGTH,则循环LENGTH次(即按照长度打印蛇)
注:Windows坐标系方向为:从左上角水平向右为X轴正方向。从左上角竖直向下为Y轴正方向。
此时输出:
如果我们将LENGTH改成30,则输出:
30个蛇身,一个蛇头。
此时程序全部代码为:
#include <iostream>
#include<conio.h>
#include <windows.h>
//初始长度
#define LENGTH 30
//输出符号
#define NoSymbol ' '
#define BodySymbol 'o'
#define HeadSymbol 'O'
using namespace std;
//窗口句柄
HANDLE handle;
//蛇体坐标数组
void ready() {
for (int i = 0;i < LENGTH;i++) {
SetConsoleCursorPosition(handle, { i,10});
cout << BodySymbol;
}
cout << HeadSymbol;//最后打印一个蛇头
}
int main() {
ready();
getch();
return 0;
}
不知道大家有没有发现,自从ready()出现以来,HANDLE handle这句语句就移到了主函数外面,这是为什么呢?
因为每个变量都有自己的作用域,只能在作用域范围内使用。
当程序执行到变量定义语句时,就在内存为此变量分配储存空间(变量被定义),当程序离开子程序(代码块,函数等,一个子程序的特征是用大括号{}括起来。)时,所有在这个子程序中被定义的变量被释放,如果下一次执行此子程序,所有变量都会被重新定义。
所以变量的生命是从定义语句开始,到它所在的大括号结束处(右括号)结束。
因此:
变量 i 的作用域是for循环内部
变量 handle 的作用域则是全局
所谓的全局变量就是没有定义在大括号内的变量,他在全局一直存在,直到程序结束。
如果handle还在main()内部,我们就无法在ready()中使用他,可以试试将handle的定义移回main(),编译不会通过。
“handle” was not declared in this scope.
“handle” 未被定义在此作用域。
其实我们还可以使用参数传递的方法:
将main()中ready()那一行改为:
ready(handle);
这个语句就是我们上一篇说的函数调用,其中handle是实际参数,简称实参。(具有值)
将ready()的函数头改为:
void ready(HANDLE handle) //函数头就是函数类型、名称、参数表所在那一行
其中handle是形式参数,又叫形参(没有值,接受实参的值)
实参形参傻傻分不清?
要知道main()中的handle和ready()中的handle是两个变量(变量在作用域内不许同名,在作用域外则无妨)
可以将ready()中的handle改为h,这个时候,handle是实参,h是形参,参数传递的实质就是:
h = handle;
即将实参的值赋给形参。
关于形参和实参的联系是:
函数调用—赋值前: 实参有值,形参无值。
函数调用——赋值时: 实参和形参值相等。
函数调用时: 形参可以发生改变,不对实参形成影响。
函数返回后: 形参被释放,无碍于实参。
还记得上一篇提到的数组和COORD类型吗?我们将在下一篇具体讲解数组,实现真正的移动!