本帖最后由 iRobot 于 2013-6-17 20:40 编辑
关于流程语句的内容,我认为没必要进行深入的解释了。1是比较简单,随便翻看一本编程书籍的第三章,都会有详细的说明。2是不要在这上面花费太多的时间。我看到过一些文章,将几个流程控制语句反复讲解,挖的很深很细致,这是没有必要的。如果在这上面花费几天时间,那学到入门水平估计得耗时半年。这节课通过几个简单的例子,让你的程序能够“停”下来或“持续”运行,算是流程控制在程序中的应用吧。
1. Button.waitForAnyPress()
让程序停下来,多用在查看屏幕输出。例如我们的HelloWorld。如果写成: - publicstatic void main(String[] args) {
- // TODO Auto-generated method stub
- LCD.drawString("HelloWorld", 0, 0);
- }
复制代码 你在屏幕上是什么也看不到的。因为程序是顺序执行的。从第一行到最后一行,执行完毕后就自动退出程序。我们代码中,最后一个有效的指令是LCD.drawString("Hello World", 0, 0);,输出文字,然后立即就退出了,以我们的肉眼凡胎,是很难看清楚的。所以应该这样写: - publicstatic void main(String[] args) {
- // TODO Auto-generated method stub
- LCD.drawString("HelloWorld", 0, 0);
- Button.waitForAnyPress();
- }
复制代码 最后一行代码的意思是等待任何按键按下,所以如果没有按键按下,程序会始终停留在这里,而上一行代码此时已经执行完毕,这样我们在屏幕上就能看到输出的文字了。这就是最简单的一种让程序“停”下来的方式。虽然简单,但是用处却不少。例如我们要查看电池的状况: - LCD.drawString(Battery.getVoltage()+”V”, 0, 0);
- Button.waitForAnyPress();
复制代码 可以看到屏幕上显示7.353V,就是当前电池的电压。
至此,我们终于把HelloWorld的知识点全部学完了。这里我说一些个人的经验供参考。有些书籍或教程,将基础部分讲述的很详细,大家也学的很有滋味,固然让人觉得学习起来很容易,但是实则有很大的坏处。将大量的时间放在基础部分的学习上,实在太奢侈了。而到了真正需要用心理解的高级内容部分,大部分人已经身心疲惫了。所以我建议,学习任何一门语言,不要在前几章上花费太多的时间。当然我的教程里对基础部分的讲解也是比较详细的,这个不要紧,我会省略中间部分,直接讲高级内容。(晕倒了。。。)
2. Thread.sleep(5*1000)
第二种方式是利用线程的sleep()方法。小括号里的参数是线程休眠时间,单位是毫秒。 所以5*1000就是休眠5秒。这里有个小技巧,就是5*1000要比写5000好。同理休眠90分钟就是90*60*1000。 因为这个函数是非线程安全的,所以要加try catch语句。这个目前不需要记忆也不需要理解,直接写就行了。 - try {
- Thread.sleep(5*1000);
- } catch (InterruptedExceptione) {
- // TODOAuto-generated catch block
- e.printStackTrace();
- }
复制代码 完整的代码如下: - public static void main(String[] args) {
- // TODOAuto-generated method stub
- LCD.drawString(""+Battery.getVoltage(),0, 0);
- try {
- Thread.sleep(5*1000);
- } catch(InterruptedException e) {
- //TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
复制代码 效果就是屏幕输出7.232V这行文字之后,停留5秒,然后全部语句就执行完毕了,程序自动退出。实际编程中,这种形式使用的比方法1要多。因为可以省掉按下一次按钮的时间。比如,我在屏幕上输出一行文字,扫了一眼,发现位置不对,没关系,5秒后自动退出了,移动一下文字的输出位置再运行,扫一眼,还有点偏,没关系,5秒后自动又退出了。明白了吧?LeJOS有个很奇怪的问题,就是程序必须退出,才可以在上面运行新的程序。这种情况很罕见,可以说不可思议。举个例子,比如你的程序运行时出错了,它就永远不可能退出。你的新程序(修正过的)就永远不能上传,卡住了。唯一的办法是用牙签去捅reset,这时你发现NXT已经被装成机器人了,根本捅不到reset。。。
虽然使用Thread.sleep(5*1000);的方式,有线程安全的问题,但是我默认为如果你可以多线程编程,就可以自行解决此问题。嘿嘿。其时简单的用一下,还是不会出问题的啦。
3. while(!Button.ESCAPE.isDown()) {}
先看一下完整的代码: - publicstatic void main(String[] args) {
- // TODO Auto-generated method stub
- LCD.drawString("" +Battery.getVoltage(), 0, 0);
- while (!Button.ESCAPE.isDown()) {
- }
- }
复制代码 Button.ESCAPE.isDown()是一个布尔值,值为true或false。while是标准的流程控制语句。感叹号是取反。这行代码的意思就是,当取消(ESCAPE)按键没有按下时,就始终执行括号内的内容。所以,在ESCAPE按钮按下前,程序执行到这里就会进入一个循环,不断的执行while语句。直到按下ESCAPE,while语句条件被破坏,程序继续执行while之后的语句,执行完毕,退出。单从这里看,和方法1是完全相同的。区别在于while是始终执行的。这就有了本质的区别了。将LCD.drawString("" + Battery.getVoltage(), 0, 0);剪切到while内部运行,程序变为:
- public static void main(String[] args) {
- // TODOAuto-generated method stub
- while(!Button.ESCAPE.isDown()) {
- LCD.drawString(""+ Battery.getVoltage(), 0, 0);
- }
- }
复制代码
两者的差异是显而易见的。修改前(包括方法1),程序只是在启动时获取一下电池状态,然后就干等着按钮按下,程序退出。即使等1年,还是显示7.232V。而放到while内之后,在按钮按下前,是不停的获取电池状态的。下一秒,电池电压变为7.1V,屏幕就显示7.1V。这种方式在编程中使用的很多。打开官方的例子会发现,涉及传感器取值的(距离,光照,马达状态),一般都是采用这种结构。
4. Button.ENTER.addButtonListener
方法4是对按钮添加监听事件。故名思议,监听到了某个事件,就执行某个操作。例如监听到ESCAPE按下,就退出程序。四种方法,没有优略之分,简单的问题,简单解决;复杂的问题,复杂解决。
按钮的监听事件涉及线程。每个监听对应一个单独线程。虽然这个监听线程是自动分配的,不需要我们干预,但是必须要牢记,线程已经随之出现了,编程时务必考虑线程。最直接的影响是,使用Thread.sleep()未必能使你的程序停下来,因为你有多个线程,一个停下来了其他的还在进行中。 下面看一个完整的例子: - public static void main(String[] args) {
- // TODO Auto-generated methodstub
- Button.ENTER.addButtonListener(newButtonListener() {
- @Override
- public voidbuttonReleased(Button b) {
- // TODOAuto-generated method stub
- }
- @Override
- public void buttonPressed(Buttonb) {
- // TODOAuto-generated method stub
- }
- });
- Button.ESCAPE.addButtonListener(newButtonListener() {
- @Override
- public voidbuttonReleased(Button b) {
- // TODOAuto-generated method stub
- }
- @Override
- public voidbuttonPressed(Button b) {
- // TODOAuto-generated method stub
- System.exit(0);//退出程序
- }
- });
- while (true) {
- LCD.drawString(Battery.getVoltage()+ "V", 0, 0);
- }
- }
复制代码 这个例子分别对确定按钮和退出按钮的按下和抬起事件进行了监听。其它部分内容都为空,只在退出按钮的按下事件里面写了一行代码: 而在代码段的最后,有一个while循环。这个while循环的条件是true,既始终执行。执行的内容是显示电池电压。此时程序就有多个线程在运行,其中一个线程不断的获取最新的电池电压并显示,另有一个线程,一旦检测到退出按钮按下,就退出程序。
方法3和方法4的重要区别在于,采用方法3的形式,一旦点击退出按钮,程序就退出了。而方法4点击退出按钮时,可以做许多事,例如返回上一级菜单,关闭网络连接等等。
要注意while (true)这行代码,是放在程序的末尾的。这和放置在程序头部有本质的区别。放置在头部时,因为条件恒成立,代码就停留在这里了,之后的代码是无法访问到的。而反过来放置在末尾,让添加监听器的代码先执行,并且被系统自动分配专用线程,这样不妨碍之后的代码执行。 因为我们已经涉及了线程,我就多说两句,加深一下印象。现在给确定按钮添加代码: - Button.ENTER.addButtonListener(newButtonListener() {
- @Override
- public void buttonReleased(Button b) {
- // TODOAuto-generated method stub
- LCD.drawString("UP"+ "", 0, 1);
- }
- @Override
- public void buttonPressed(Button b) {
- // TODOAuto-generated method stub
- LCD.drawString("DOWN"+ "", 0, 1);
- while(true)
- ;
- }
- });
复制代码 while中的内容可以自行填写,例如按下确定按钮之后,开始获取光线传感器传回的数据。这是一种很常见的用法,但是不注意的话,就会出现大问题。所以我在按下和松开按钮时,都添加了一句代码,用于在屏幕上显示按钮的状态(DOWN还是UP)。实际运行程序之后可以看到,按下时显示DOWN,但是松开时却不显示UP,既buttonReleased事件其实是访问不到的。为什么呢?很简单。虽然代码写在一个委托中(我不知道Java中是不是管这个叫委托,反正形式上确实是一种委托)的两个函数里,但是却运行在同一个线程中,两个函数依旧是顺序执行。这就回到上面讲的啦,while条件始终成立,后面的代码(buttonReleased)无法访问。解决办法是把while放在单独的线程中执行: - public void buttonPressed(Button b) {
- // TODOAuto-generated method stub
- LCD.drawString("DOWN"+ "", 0, 1);
- Thread thread = new Thread(new Runnable() {
- @Override
- public void run() {
- //TODO Auto-generated method stub
- while(true)
- ;
- }
- });
- thread.start();
- }
复制代码 这时,while在自己的线程中,就不会阻塞住buttonReleased事件了。
5. 环境光探测器
大家还记得我之前做过的光波塔吧?当时随手写了一个程序,用于检测环境光的。程序比较简单,正好和这次的课程比较相符,送上源代码供大家参考。 先上图:
再上视频:
最后是代码: - import java.util.ArrayList;
- import java.util.List;
- import javax.microedition.lcdui.Font;
- import javax.microedition.lcdui.Graphics;
- import lejos.nxt.Button;
- import lejos.nxt.ButtonListener;
- import lejos.nxt.ColorSensor;
- import lejos.nxt.LCD;
- import lejos.nxt.SensorPort;
- import lejos.nxt.ColorSensor.Color;
- public class NXTLightDraw {
- // static List<MyPoint> lst = new ArrayList<MyPoint>();
- static ArrayList lst = new ArrayList();
- static boolean isRun = true;
- static int inteval=200;
- /**
- * @param args
- */
- public static void main(String[] args) {
- // TODO Auto-generated method stub
- Graphics g = new Graphics();
- DrawFrame(g);
- int count = 0;
- ColorSensor cs = new ColorSensor(SensorPort.S1);
- cs.setFloodlight(Color.NONE);
- Button.ENTER.addButtonListener(new ButtonListener() {
- @Override
- public void buttonReleased(Button b) {
- // TODO Auto-generated method stub
- }
- @Override
- public void buttonPressed(Button b) {
- // TODO Auto-generated method stub
- isRun = !isRun;
- }
- });
- while (!Button.ESCAPE.isDown()) {
- int raw = cs.getRawLightValue();
- int val = cs.getLightValue();
- if (lst.size() > 79) {
- lst.remove(0);
- }
- // lst.add(new MyPoint(count, (int) raw / 18));// 900/50=18
- lst.add(raw > 900 ? (int) 900 / 18 : (int) raw / 18);
- if (count < 80)
- count++;
- if (isRun)
- DrawLine(g);
- try {
- Thread.sleep(inteval);
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
- private static void DrawFrame(Graphics g) {
- // TODO Auto-generated method stub
- g.drawLine(14, 55, 14, 4);
- g.drawLine(14, 55, 96, 55);
- g.setFont(Font.getFont(0, 0, Font.SIZE_SMALL));
- g.drawString("8s 16s 24s 32s 40s", 18, 57, 0);
- g.drawString("150", 0, 47, 0);
- g.drawString("300", 0, 38, 0);
- g.drawString("450", 0, 29, 0);
- g.drawString("600", 0, 20, 0);
- g.drawString("750", 0, 11, 0);
- g.drawString("900", 0, 2, 0);
- }
- public static void DrawLine(Graphics g) {
- g.setColor(Color.WHITE);
- g.fillRect(15, 5, 80, 49);
- if (lst.size() > 0) {
- for (int i = 0; i < lst.size(); i++) {
- LCD.setPixel(i + 14, 55 - (int) lst.get(i), 1);
- }
- }
- }
- }
复制代码
6. 后记
首先说一句,让你的程序停下来,其本质就是流程控制。并不局限于我提到的四种方法,其实还有很多。但是这四种应该够用一阵子了。你可以生搬硬套,虽然生搬硬套的效率不高,但是不要紧,写程序效率不是问题。 新手花费时间提高代码效率,不如花同样的时间学下一章节。即便是高手,也不考虑效率问题。为什么呢?因为待解决的问题太多。。。
下一节课讲解如何搭建Eclipse环境。这个很重要。没有Eclipse,等同于武将上战场不拿大砍刀。。。好吧,我觉得Eclipse充其量就算是大砍刀吧。。。
|