写给设计师的OF编程指南6 - 程序流程控制-条件语句 (上)
上节提到,循环语句可以让某段代码反复执行,如果我们用一根线条来去类比程序的执行流程,它就像其中打圈的部分。而后面介绍的条件语句,则会使程序创建多个分支,呈现树根一样的形态。
这节主要展示条件语句 if 的特性,同时辅以多个知识。你会了解到如何使用逻辑运算符,关系运算符,以及 keyPressed 事件。
下面先铺垫有关数据类型以及关系,逻辑运算符的基础知识。
数据类型 bool,char,string
数据类型 | 含义 | 作用 |
---|---|---|
int | 整形 | 储存整数数据 |
float | 浮点形 | 储存小数数据 |
char | 字符类型 | 储存字符 |
string | 字符串类型 | 储存字符串 |
bool | 布尔类型 | 保存 true 值或 false 值 |
前面提到的 int ,float 可以保存数值数据。但显然是不够用的。例如我们想储存一些中文或是英文字符,就需要用到 char 和 string。
代码示例(6-1):
void ofApp::setup(){
char a = 'x';
string b = "words";
cout << a << endl;
cout << b << endl;
}
控制台输出结果:
对照上面的示例。char 是一种用于存储单个字符的数据类型。赋值时,字符必须带上单引号,以此与变量名区分开来。而对 string 类型进行赋值,字符则需要使用双引号。它可以比 char 存储更多的字符信息。假如我们希望在程序中储存地址,对话,人名等信息,就可以考虑用 string。
既然 string 存储的信息比 char 更多,那能否直接将 char 类型赋值给 string 类型呢?是可以的,前提是需要写上一个类型转换函数,否则程序会报错。
代码示例(6-2):
void ofApp::setup(){
char a = 'x';
string b = ofToString(a);
cout << a << endl;
cout << b << endl;
}
控制台输出结果:
这里的ofToString(),就是类型转换函数。它能够将 char 类型转换成 string 类型。
连接字符串
string 类型有一个特殊的用法,使用“+” 号可以将两个字符串连接起来。
代码示例(6-3):
void ofApp::setup(){
string a = "words1";
string b = "words2";
string c = a + b;
cout << c << endl;
}
控制台输出结果:
当我们希望将多个文本信息组装在一起,就可以考虑 + 号。
bool类型
下面介绍另一种数据类型 - bool。它能存储的东西非常少,只有两个值,true(真) 与 false(假)。就像抛一个没有厚度的硬币,要么正面,要么反面。非黑即白,没有中间状态。布尔类型会与后面提到的 if 语句结合得非常紧密,根据 true 值或 false 值,可以决定某些语句是否执行。
先看一个有关bool的例子
代码示例(6-4):
void ofApp::setup(){
bool a = true;
bool b = false;
cout << a << endl;
cout << b << endl;
}
控制台输出结果:
在 OF 中你会发现,控制台中并不会直接输出 true 或 false,你只能看到 1 与 0。这里的 1 其实就代表 true ,0 就代表 false 值。反过来,除了用 true ,false 来代表真假值之外,还能用数字来进行赋值。
代码示例(6-5):
void ofApp::setup(){
bool a = 1;
bool b = 0;
bool c = 3;
bool d = 3.2;
cout << a << endl;
cout << b << endl;
cout << c << endl;
cout << d << endl;
}
控制台输出结果:
OF中可以对 bool 类型的变量直接赋整形数据又或是浮点型数据。而无需像 P5 一样需要类型转换函数。对于所有“非0”的数字,都会处理成 true 值。而当数字为 0 ,则会处理成 false 值。
P5 与 OF 的异同对比
要特别注意的是,P5 中的 String 类型为大写,OF 中的 string 为小写。布尔类型 P5 中写作 boolean ,OF 中写作 bool。两者十分相似,请不要混淆。
关系运算符与逻辑运算符号
在程序中不仅能做加减乘除的运算,还有一些我们意想不到的运算方式。
关系运算符
像加减乘除这类运算符,最终返回的结果都是数值。而关系运算符,返回的则是布尔值。
关系运算符 | 含义 | 调用方法 |
---|---|---|
== | 等于 | a == b |
!= | 不等于 | a != b |
> | 大于 | a > b |
>= | 大于等于 | a >= b |
< | 小于 | a < b |
<= | 小于等于 | a <= b |
代码示例(6-6):
void ofApp::setup(){
int a = 1;
int b = 2;
cout << (a == b) << endl;
cout << (a != b) << endl;
cout << (a > b) << endl;
}
控制台输出结果:
顾名思义,关系运算符就是用作比较左右两边的关系,非常浅显易懂。当条件成立的时候,就返回 ture 值,不成立则返回 false 值。其中唯一要注意的是“==” 的写法,必须写两个等于号才是关系运算符。只写一个就成了赋值“=”符号。
逻辑运算符
接下来轮到介绍逻辑运算符,它返回的一样是布尔值。并且有三种类型:与,或,非。
逻辑运算符 | 含义 | 调用方法 |
---|---|---|
&& | 与 | a && b |
|| | 或 | a || b |
! | 非 | !a |
|| 运算符的满足的条件则宽松一些,只要其中有一边为真,返回的就是 true 值。两边为假,返回的就是 false 值。使用 && 运算符时,只有当表达式两边都为真时,返回的才会是 true 值。当其中一边为假,又或是两边都为假,返回的会是 false 值。
! 运算符的作用是取反。它需要直接写在表达式前面,而不存在需要比较左右两边的情况。若原有的表达式为真,使用后返回的结果则为 false。若原来为假,返回的结果则为 true。
代码示例(6-7):
void ofApp::setup(){
cout << (true && true) << endl;
cout << (true && false) << endl;
cout << (false && false) << endl;
cout << (true || true) << endl;
cout << (true || false) << endl;
cout << (false || false) << endl;
cout << !true << endl;
cout << !false << endl;
}
控制台输出结果:
条件判断语句 if
前面提及的概念都比较抽象。似乎也很难想通,关系运算符与逻辑运算符具体能有何作用。其实只要放在图形创作的语境下去理解,一切都会非常直观。这节的主角终于来了,那就是 if 语句。
下面是 if 语句的一般调用方式:
if(表达式){
}
在没有学习 if 语句之前,写在程序中的语句,都是必须执行的,没有选择的机会。而使用了 if 语句后,在执行前需要先判断表达式的值。当表达式的布尔值为 true 的时候,大括号内的代码则会执行,否则会跳过。
代码示例(6-8):
void ofApp::setup(){
if(true){
cout << 1 << endl;
}
if(3 > 2){
cout << 2 << endl;
}
if(false){
cout << 3 << endl;
}
if(3 < 2){
cout << 4 << endl;
}
}
控制台输出结果:
你可以通过以上的输出值,看到哪些语句被真正执行,它们都与表达式的布尔值密切相关。
if 语句控制图形的显示与隐藏
现在我们可以不看黑乎乎的控制台了,尝试用 if 实现些简单的图形效果。
代码示例(6-9):
void ofApp::setup(){
ofSetWindowShape(700,700);
}
void ofApp::draw(){
ofBackground(32,48,65);
if(mouseX > ofGetWidth()/2){
ofSetColor(3,240,175);
ofDrawCircle(ofGetWidth()/2,ofGetHeight()/2,100);
}
}
将鼠标的位置的判断写在 if 中,就能影响后续的语句是否执行,从而达到控制图形显示或是消失的效果。
if 语句的扩展 - else 与 else if。
if 语句只有在满足条件时,才会执行大括号中的代码。上例表达式的条件是mouseX > ofGetWidth()/2 ,所以鼠标一旦移到屏幕右方,就会显示圆形。假如我们希望鼠标移到屏幕左方出现的是另一个图形。应该怎么办?需要多写一个 if 语句?
大可不必,如果两个条件之间是非此则彼的关系,那直接用 else 会更方便。
调用形式:
if(条件){
语句1;
}else{
语句2;
}
满足条件时,执行语句 1。不满足则执行语句 2.
代码示例(6-10):
void ofApp::setup(){
ofSetWindowShape(700,700);
}
void ofApp::draw(){
ofBackground(32,48,65);
if(mouseX > ofGetWidth()/2){
ofSetColor(3,240,175);
ofDrawCircle(ofGetWidth()/2,ofGetHeight()/2,100);
}else{
ofSetColor(204, 3, 204);
ofSetRectMode(OF_RECTMODE_CENTER);
ofDrawRectangle(ofGetWidth()/2,ofGetHeight()/2,200,200);
}
}
这样写就简便多了。除此之外,关于 if 还有另一种用法,那就是 else if。当我们有更多的条件需要判断时,就可以考虑它。
调用形式:
if(条件 1){
语句 1;
}else if(条件 2){
语句 2;
}
...
else if(条件 n){
语句 n;
}else{
语句 n+1;
}
else if 语句是可以不断叠加。它的执行顺序依然是自上而下,先从条件 1 开始进行判断。若条件 1 满足则执行语句 1,同时后面的语句都跳过,不执行。当条件 1 不满足,才会检测 else if 中的条件 2。若条件 2 满足,则执行语句 2。否则就往复循环,直到执行为止。最后,如果所有条件都不满足,则执行 else 中的语句。
当然,最后的 else 也不是必须添加的。如果你不写 else,并且所有条件过一遍后,仍不满足,那 if 中的所有语句都不会执行。
代码示例(6-11):
void ofApp::setup(){
ofSetWindowShape(700,700);
}
void ofApp::draw(){
ofBackground(32,48,65);
if(mouseX < ofGetWidth()/3){
ofSetColor(3,240,175);
ofDrawCircle(ofGetWidth()/2,ofGetHeight()/2,100);
}else if(mouseX < ofGetWidth()/3 * 2){
ofSetColor(204, 3, 204);
ofSetRectMode(OF_RECTMODE_CENTER);
ofDrawRectangle(ofGetWidth()/2,ofGetHeight()/2,200,200);
}else{
ofSetColor(3, 165, 204);
ofDrawTriangle(250, 450, 450, 450, 350, 250);
}
}
这个例子就相当于把屏幕划分成了 3 块。左,中,右。鼠标横坐标不同,显示的图形也不同。
if 的嵌套
if 语句是可以嵌套使用的。可以由此组合出更复杂的情况。
代码示例(6-12):
void ofApp::setup(){
ofSetWindowShape(700,700);
}
void ofApp::draw(){
if(mouseX > ofGetWidth()/2){
ofBackground(235,16,0);
if(mouseY > ofGetHeight()/2){
ofSetColor(32, 48, 65);
ofSetRectMode(OF_RECTMODE_CENTER);
ofDrawRectangle(ofGetWidth()/2,ofGetHeight()/2,200,400);
}else{
ofSetColor(255);
ofSetRectMode(OF_RECTMODE_CENTER);
ofDrawRectangle(ofGetWidth()/2,ofGetHeight()/2,400,200);
}
}else{
ofBackground(48,180,89);
if(mouseY > ofGetHeight()/2){
ofSetColor(32, 48, 65);
ofDrawEllipse(ofGetWidth()/2,ofGetHeight()/2,200,400);
}else{
ofSetColor(255);
ofDrawEllipse(ofGetWidth()/2,ofGetHeight()/2,400,200);
}
}
}
由于在第一层 if else 的两个分支里,写入了不同的背景颜色。所以鼠标左右位置变化,就会直接影响背景色。后面在两个分支内,又分别写入了一个 if else。通过多重状态的组合,就相当于将程序划分成了 4 个区域,4 种状态。
if 语句与取模运算
这里介绍一个比较巧妙的用法。它在 for 循环中使用频率非常高。过去我们一直通过 for 循环的 “i” 值,来影响绘图元素的部分参数。例如画一排圆,就需要用到 “i” 值来确定位置坐标。除此之外,我们其实可以对这个 “i” 作一些判断。比如进行取模运算(相当于求余数)。根据余数数值的不同,结合 if 语句就可以对大量元素进行分组处理。下面的例子中,当 “i” 为偶时,元素的填充色便设置为白色。否则(奇数时),填充色则为黑色。
代码示例(6-13):
void ofApp::setup(){
ofSetWindowShape(700,700);
}
void ofApp::draw(){
ofBackground(132,197,100);
int num = 10;
float w = mouseX / 10;
for(int i = 0;i < num;i++){
if(i % 2 == 0){
ofSetColor(255);
}else{
ofSetColor(0);
}
ofSetRectMode(OF_RECTMODE_CENTER);
ofDrawRectangle(ofGetWidth()/(float)num * (i + 0.5),ofGetHeight()/2,w,600);
}
}
KeyPressed 事件
现在相信你已经理解 if 的特性了。但要让它更好玩,你还需要用到后面介绍的一些编程概念。接着要介绍的,就是 keyPressed 事件。
前面与程序发生交互,基本上都是靠鼠标坐标 mouseX,mouseY 来实现的。虽然灵活,但局限性也很明显。当我们想通过鼠标位置来做选择的时候,就得写许多许多的if。在还没掌握如何写一个按钮控件的情况下。用键盘来做交互才是最明智的方式。
keyPressed 是与键盘相关的事件。它和 setup,draw 一样。在程序中已经事先定义好。你只需要把它的结构写进去,就能直接调用。
OF 中由于已经事先写好了 keyPressed 事件的结构,所以你无需像 P5 一样自己手写一遍。只需往里面填充代码即可。
下面先通过一个示例,理解 keyPressed 对程序流程的影响。
keyPressed 的运行流程
代码示例(6-14):
void ofApp::setup(){
cout << ofGetFrameNum() << ":setup" << endl;
}
void ofApp::update(){
cout << ofGetFrameNum() << ":update" << endl;
}
void ofApp::draw(){
cout << ofGetFrameNum() << ":draw" << endl;
}
void ofApp::keyPressed(int key){
cout << ofGetFrameNum() << ":keyPressed" << endl;
}
代码说明:
运行程序后,记得在键盘上任意按下一些键,然后关闭程序,查看控制台的输出
这里主要观察各个结构的执行顺序,其中 ofGetFrameNum() 可获取程序当前已运行的帧数
在控制台处往下翻,寻找带有“keyPressed”的字符串。它的输出位置,取决于你的按键时间。在这个例子中就表示,程序运行到 26 帧时,就触发了按键事件。
通过观察 ofGetFrameNum(),不难发现 keyPressed 事件是在 update 前才执行的
cout 可以直接输出整形数据。所以直接写 cout << 1 << endl 是可行的,但当你希望使用 cout 输出字符,字符本身则需要加上双引号,如 cout << “:update” << endl;,这表示输出的是字符串类型,否则会出错
由于 ofGetFrameNum() 是整形数据,而“update” 是字符串类型。因而不能直接写成“ cout << ofGetFrameNum() “:update” << endl; ”。正确写法是在中间多加一个 “<<” 来分隔开
另外也存在另一种写法 “ cout << ofToString(ofGetFrameNum()) + “:update” << endl; ” ,就是用 ofToString 函数先将 ofGetFrameNum 转换成字符串类型,这时用 “+” 号就能连接两个字符串了
上图揭示了 keyPressed 的运行流程
keyPressed 获取按键键值
keyPressed 不仅能判断你何时按下按键,通过获取 key 的值,还能知道具体按了哪个键。
代码示例(6-15):
void ofApp::keyReleased(int key){
if(key == '1'){
cout << 1 << endl;
}
if(key == '2'){
cout << 2 << endl;
}
if(key == '3'){
cout << 3 << endl;
}
if(key == 'a'){
cout << 'a' << endl;
}
if(key == 's'){
cout << 's' << endl;
}
if(key == 'd'){
cout << 'd' << endl;
}
}
上面例子对数字键 1,2,3,以及字母 a,s,d 添加了按键检测。当你按下这几个键。就会在控制台输出对应的字符。key 是系统定义好的,它会自动获取你按下的键值。所以若想了解 key 是否为某个值时,判断条件就需要写成 if(key == 某值)。当你想检测的是数字键或是字母键,只要在数字或是字母的基础上加单引号即可。
对于某些无法用数字或是字母表示的特殊按键,有另外的写法
代码 | 代表键值 |
---|---|
OF_KEY_UP | 方向键上 |
OF_KEY_DOWN | 方向键下 |
OF_KEY_LEFT | 方向键左 |
OF_KEY_RIGHT | 方向键右 |
OF_KEY_SHIFT | shift 键 |
OF_KEY_DEL | Del 键 |
OF_KEY_ALT | Alt 键 |
OF_KEY_CONTROL | Ctrl 键 |
OF_KEY_RETURN | 回车键 |
OF_KEY_TAB | Tab 键 |
OF_KEY_F1 | F1 |
OF_KEY_F2 | F2 (以此类推) |
因此,若想检测向上的方向键,就可以这么写 if(key == OF_KEY_UP){ 语句;}
需特别注意的是 OF_KEY_ALT , OF_KEY_SHIFT ,OF_KEY_CONTROL 都会执行两遍,你可以通过 cout 观察结果
keyPressed 控制图形
前面通过鼠标的位置,来控制图形的显示或是隐藏。现在可以改用 keyPressed 试试。
代码示例(6-16):
—-ofApp.h 内
bool show;
—-ofApp.cpp 内
void ofApp::setup(){
ofSetWindowShape(700,700);
}
void ofApp::update(){
}
void ofApp::draw(){
ofBackground(32,48,65);
if(show){
ofSetColor(3,240,175);
ofDrawCircle(ofGetWidth()/2,ofGetHeight()/2,100);
}
}
void ofApp::keyPressed(int key){
if(key == '1'){
show = true;
}
if(key == '2'){
show = false;
}
}
绘图函数不能写在 keyPressed 中,只能通过创建一个布尔变量来作为中介。keyPressed 先影响布尔变量的值。从而 draw 函数中的 if 语句再根据此值来决定显示与否。
有关 keyPressed 的用法先介绍到这里。后面会有更多的篇幅去深入理解各种事件。
条件控制语句 switch
条件控制语句不只有 if。if 语句还有一个兄弟,那就是 switch。
写法格式:
switch(表达式) {
case 常量表达式1:
语句 1;
break;
case 常量表达式2:
语句 2;
break;
....
case 常量表达式值n:
语句 n;
break;
}
switch 后需要写一个表达式,后面的 case 语句写的是这个表达式值可能出现的条件。当此值满足条件,对应的语句就会执行。
case 可以叠加多个,并且后面必须填写常量。
需要在每个case 的末尾都加上 break,这是一个固定写法。代表跳出语句。
若是不加 break,则 break 后的下一个 case 语句,会直接执行。
switch 和 if 非常的相似,那何时用 switch ,何时用 if?可以对照前面的例子(6-15),并且熟悉一下 switch 的用法
代码示例(6-16):
void ofApp::keyPressed(int key){
switch(key) {
case '1':
cout << 1 << endl;
break;
case '2':
cout << 2 << endl;
break;
case '3':
cout << 3 << endl;
break;
case 'a':
cout << 'a' << endl;
break;
case 's':
cout << 's' << endl;
break;
case 'd':
cout << 'd' << endl;
break;
}
}
用 switch 语句来判断键值,比起用 if 更简洁直观。
当程序需要处理多个分支,并且需要判断的都为常量值时,可以考虑使用 switch。
switch语句中的 case 要求的是常量,一般不能进行逻辑判断。如果内层的判断条件是诸如“x > 5”之类的,就不能使用 switch,而是需要考虑 if 语句。
switch 的扩展
switch 和 if 类似。if 当中的 else,只有在前面的条件都不满足时,才会执行其中的语句。在 switch 中与之相对应的就是 default。它总是放在最后。
写法格式:
switch(表达式) {
case 常量表达式1:
语句 1;
break;
case 常量表达式2:
语句 2;
break;
....
case 常量表达式值n:
语句 n;
break;
default:
语句 n+1;
}
default 后无需加上 break 语句。
End
关于条件控制语句就介绍到这里。下节将会介绍加载文字图片和调用插件的方法。这些知识点将与 if 糅合到一起,你可以从零开始制作一个文字冒险游戏!