QB经典教程

      编程宝典 2005-6-5 14:39:00

这是转贴,不是我写的

很久很久以前,高中时代,我就从QB起家琢磨编程,入门后很快就学会
汇编语言,再后来,C,VB,JAVA……越来越容易。
不过QB现在已经风光不在,难得还有人提起。

以前在拨号BBS的时代,我给网友写过几篇QB教程,但没写完 -- 但
入门足够了,其中所讲的语法即使转到VB下仍然也适用。

(别见怪,那时候我的ID叫做 Float Pointer)

- 信区: PROGRAMR.CHINA [程序人生]-----------------------------------
--------
信件: 2332 日期: 26 Oct 98
11:30:37
来自: Float Pointer 已读: 是 已回
信 : 否
给: All 标记:

主题: FP的QB教程 第一篇
----------------------------------------------------------------------
--------

看到很多站友想学QB, 我就想写篇教材, 把我几年来的经验心得
写出来. 但从未写过教材, 怕出丑, 因此先在舒克本地编程讨论
区写了4篇试试. 其实这教程就是给DING XUAN站友写的, 其中他
提了不少意见, 里面的Q> A>问答也是我们的部分讨论.
通过写教程, 我自己受益也很多, 比如我在写例子时才发现QB也
能做出流畅的三维动画的, 以前都没想过.
但我很忙, 以至于隔了一个月才整理出来前几篇, 而且第五篇至
今也未写完.
我的写作思想是以有限的实例函盖更多的内容, 以我自己的经验
写出而不参考前人书本, 力求生动而不是枯燥无味.
但前两章总觉得不满意, 怎么改也不好, 因为打基础的部分.

有任何问题和意见可以直接回信问我, 但我不一定马上就回, 隔
一两个星期都没准. :P

咳..咳... 啊啊,咪伊伊,吗啊啊啊~ (练练嗓子) ;)

-0-
QB有别与老式BASIC,首先它不再是纯解释BASIC,而可以编译
成EXE文件,其次引入模块化概念,使程序更加清晰,不容易出错。
而且还增加许多其他的功能。
学习它可以对你以后学其他语言打基础,因为QB模块化的概念
与C、PASICAL等相似,各种流程控制也相似。因为VB是在QB的基础
上制作的,语法几乎完全相同,把QB的模块化扩展成简单的OOP
(面向对象)编程,而界面完全用鼠标来画出来,添入各种属性(
如颜色,标题等),再添入QB的代码就出来一个漂亮的WINDOWS程序。
因此对于学习VB,QB将为你打下坚实的基础。我在全国编程区见过许
多站友问一些VB的问题,其实那都是QB的基础知识。
当然, 一些简单的小程序用QB做也是最方便的, 要不然M$怎么会
在DOS里带个QBASIC.

费话不多说。
首先你要有个QB的编程工具。DOS命令QBASIC不可以,因为它是
个简易版的QB,版本才1.X,虽然有大部分QB的功能, 但不能完成本
文所写的所有工作。QB建议用4.5版,它是最后一个版本的QB。但仍
然有BUG,比如某些汉字不能输入。
等你学到最后,可以用调用系统中断的方法输入汉字,但很麻烦。不
如你直接用MSBASIC7.X,这是DOS下最后一个BASIC,很完善,可以输
汉字。 另外VB FOR DOS 1.0也可以用,不画界面就成了QB。
安好QB后首先把OPTION菜单的FULL MENUS选上,这样才能用所有
的功能。

-1-
我们先编个很简单的小程序:
在上面那个窗口直接输入下面这段程序:
--- 例 1-1
SCREEN 12
FOR X=0 TO 639
LINE(0,0)-(X,479),X MOD 16
NEXT
END
输入完后按一下F5键就可以运行了。如何?吃惊吧,这么漂亮! :)
这个小程序只是为了提高你学习的兴趣,要自己编还要再努力。 :)
从这个小程序我们学到什么呢?
输入程序,按F5就可以直接运行!如果你想编译成EXE文件,首先要
存盘(FILE菜单),然后选RUN菜单MAKE EXE FILE,选STAND-ALONE EXE
这样一个EXE文件就能运行,否则还需要运行库。MAKE EXE AND EXIT后,
在命令行输入你刚才定的那个文件名就成了。
在QB环境里运行时,任何时刻你都可用CTRL-BREAK暂停,不会因为你的
错误而死机。但如果做成EXE文件,CTRL-BREAK就不管用了。因此你要
先把程序先按F5的方式运行,调试好再编译成EXE文件。

如果你用小写输入上面这段程序,会发现SCREEN,FOR,LINE,MOD,NEXT,
END都会自动变大写,因为这些都是系统识别的保留字,有特殊的用途,
你不能当作变量名来用。如果你输入错误,系统可能会自动报告并提示
修改,比如你把那个16删了试试看。QB的特点是为初学者想的很周到,
不容易犯错。

QB程序以命令为单位,每行都是一个命令,每行也可写许多命令,用冒
号分隔。比如 SCREEN 12:FOR X=0 TO 639:...
显然,这不利于阅读。

按F6键可以切换到下面那个Immediate窗口,称为“立即窗”,在那个窗
口输入
PRINT 234*10
然后一回车你就能在屏幕上看见结果。如果没看清,按F4就可以再看看。
也就是说,在这个窗口输入的任何命令都是立即执行而不需要按F5的,
这为调试程序提供了很大的方便。

在DEBUG菜单里有很完善的调试工具。你的程序不可能一次成功,如果
有BUG,可以用QB强大的调试功能进行调试。 WATCH WINDOWS可以显示
你所要知道的变量,break point断点可以在你所指定的地方自动停下
来。F8和F10可以单步运行程序,区别是F10可以跳过过程函数(以后再说)
其他菜单你自己琢磨琢磨就知道干什么用了.

最后,QB的帮助系统很完善,只要把光标移到你所要查的命令上,比如
SCREEN,按F1就能看到关于SCREEN的各种用法,细节和使用范例了。
也可以从HELP的INDEX里找到。习惯于读HELP对任何语言的编程有非常有
用,我只能带你入门,要成为高手,无论什么学语言都要仔细研究HELP。
有空你可以把QB帮助里的那些EXAMPLE都运行一遍,这样你就能透彻的
懂得这些命令如何使用了。

下面我列出各种热键:
F1 帮助 ALT-F1 上一个帮助主题
F2 模块(过程和函数)列表
F3 查找下一个
F4 看运行结果
F5 运行 SHIFT-F5 重新运行
F6 切换窗口
F7 运行程序到当前光标处
F8 简单的单步运行
F9 断点设置
SHIFT-F9 增加一个表达式到WATCH窗口,在调试时可以直接在WATCH窗口里看
到这个表达式的结果.
F10 单步,但自动跳过过程(函数)
SHIFT-F8 向后跟踪运行历史
SHIFT-F10 向前跟踪运行历史
CTRL-BREAK暂停运行
虽然可以从菜单里选取, 很快你就会发现这些热键非常有用而且常用,
甚至你不用死背.

-2-
这部分我们开始进入真正QB语法的学习。打基础是很无聊的,因为没有有趣
的程序,那么多概念要硬记。但打不好基础以后也学不好。
鉴于读者已有一些编程概念,我就不细讲什么叫变量,什么叫函数什么是命令
了。 :)
下面这些内容开始也许不容易懂, 也很枯燥, 你也不必细看, 有些印象就成,
因为在编程中时时要用到它们, 用时你可以现查, 查一两次就记住了. ;)

☆QB的数据表示

--- 数字的表示方法:
在QB里数字的表达跟平常一样,可用负号,小数点,小数点前的0可
省略比如.1就是0.1。
整数的八进制表示法是在开头写上&,如果是八进制长整数1234567
就是 &1234567& (后面的&表示长整数, 见后面)
现在已经很少用8进制数了.
十六进制表示法是在数字前加 &H,这很常用.
比如我们要把一个16进制数转换会10进制,在立即窗里输入:
? &HFF
输出结果:
255
由于在计算机内部常以16进制运算, 希望你能清楚的了解16进制数.
比如&hFF是一个字节内存表示出来的最大数, 因为再加1等于&H100.
而两个字节就是 &H100 * &H100 -1 = 65536 -1=65535
由于有正负之分,除以2, 等于32767+32768的和. 现在你应该知道
为什么整数的范围是 +32767 到 -32768了吧?
同样的, 长整数是4个字节表示的, 多大范围你可以自己算. :)
可惜QB没有二进制数, 多数语言也没有.

带小数的浮点数只能用10进制的.所谓浮点表示法就是科学计数法,
单精度是用E表示指数:
3.14E+5 就是 3.14*10^5 (10的5次方)
双精度用D表示:
-3.13e-20 就等于 -3*10^(-20) (10的负20次方)

--- QB的数据类型

数据类型是程序的灵魂,大学里专门有一门课叫做“数据结构”,是非常
无聊的一门课,但却非常重要。 :)
数据类型在数据库语言里是以应用决定的,QB和其他语言的基本数据类型是
由硬件所决定的。在数学中似乎只有数字这一种变量, 由于硬件决定计算机
语言必然要用不同的类型, 它们都很有用,不同的类型会有不同的用途和效果.

QB的数据类型有下面这些:
类型 后缀符号 简称 名称 长度(字节) 范围 举例
整型 % INT INTEGER 2 +-32767 A%
长整型 & LNG LONG 4 约+-20亿 A&
浮点 ! SNG SINGLE 4 见下 A!
长浮点 # DBL DOUBLE 8 见下 A#
字符串 $ STR STRING ? 32767字符 A$

VB里还增加一种商业数据类型,后缀是@,有两位小数。
这是因为浮点类型会有误差, 可能出现0.02-0.01=0.009999的情况.
对于绘图运算这没什么, 对于商业运算就很危险了.
对于QB你可以用整型或长整型替代, 以分为单位就成了.

BTW:&读做“俺的”,$读做“刀乐”,@读做“爱特” ;)
注意:浮点数与其他语言不同,不是FLOAT。
此外还有定长字符串和自定义类型。
变量名可以用任何一个非保留字的字母和数字组合,必须以字母开头。
保留字就是QB的命令和函数已经用了的单词。 大小写都没关系,
QB会自动统一大小写。
注意不能用"_",QB不支持。VB等支持, 但有特别用途。

变量定义只要在变量名后加上后缀就可以指定变量类型,比如a%。
注意 a%和a$是两个不同的变量,可以同时使用。
如果不加任何后缀,系统默认是浮点类型的。
数字也是如此, 23%表示整数23, 23!代表浮点数23
当然 23.0 也是浮点数.

使用DEF后紧跟“简称”可以改变系统默认类型。
比如DEFINT A 代表所有以A开头的变量都是整型变量。
比如DEFLNG A-Z 代表所有变量(从A到Z开头)都是长整型变量。
此外还可以用DIM命令+名 AS +类型名称 指定变量类型:
DIM A AS INTEGER
则A是整型变量。


--- 数据类型都是有范围的:
最大 最小
INTEGERS 32,767 -32,768
LONG 2,147,483,647 -2,147,483,648
SINGLE (7位数字) ?.402823 e+38 ?.401298 e-45
DOUBLE (15位数字) ?.7976931 d+308 ?.940656 d-324
字符串 32767个字符 0

如果所计算出的数据超过数据类型所允许的范围则会发生溢出错误。
看到这里你可能会问,如果我们使用DOUBLE类型不就全解决问题了么?
由于处理范围越大需要更多的内存去处理,速度也就越慢。
而浮点数的处理速度是永远比不上整数的(即使你用的是原装奔腾)。
因此占内存最小的整型(2BYTE)速度最快,而且能满足一般的应用,
我们一般在程序第一行加上DEFINT A-Z,这样可以成倍的提高运行速度。

数字也可指定类型,默认是整型的。如果不指定可能会出错,
看下面这个例子,在立即窗里输入:
? 32767 * 2
结果出现一个OVERFLOW(溢出)错误。如果用:
? 32767& * 2
就能输出正确的结果了。


--- 字符串
普通字符串长度是任意可变的,比如a$="asdfasdf":a$=""(空串)
字符串以两个双引号括起来。如果有引号或回车等其他字符怎么办?
可以用CHR$函数,比如CHR$(13)代表回车, 13是回车的ASCII值。
字符串可以用加法合并:
?"asdfasdf"+chr$(34)
输出:
asdfasdf"

--- 字符串还有定长字符串,长度由DIM命令指定,赋值时如果不够长度
用空格填补,超过长度自动截断,这类似数据库的C型字段,也正是
为便于处理数据库而设计的。比如:
DIM S AS STRING*10
则S字符串的长度永远为10

在这里先说一下注释命令REM或"'"。凡是以这两个开头的代表是注释
而不是程序,QB不会执行。比如:

REM asdsf sdfgdf sdfgg QB不会说这行打错了
' PRINT 1345234 随便你怎么打都没关系,QB不执行这行

用这个命令(两种写法都一样)可以注释你的程序是干什么用的,
以免以后忘了,也可以在调试时不暂时不用的命令用"'"注释掉。

--- 数组类型.
QB的数组用DIM定义,比如:
DIM ARR(12,10) AS LONG
表示一个有13行,每行11个长整型数的数组(矩阵).
数组最小下标是(0,0),最大是(12,10) (这与C语言不同,最大下标多一)
最小下标可以这样定义:
DIM ARR(1 TO 12,10 TO 20)
则最小下标就是arr(1,10)了.
注意,arr和arr(0,0)是两个没有关系的变量,可以同时用,不象C语言.

--- QB里没有逻辑型数据
任何数字类型都可代替逻辑值.用0代表假,-1代表真,因为-1的二进制
表达法是 11111....,正好与0000....相对应,任何数据类型都是这样.
其实在判断语句里,非0就代表"真"了.
比如在立即窗里输入: IF 10 THEN ?"TRUE"
... 利用立即窗你可以实验各种命令如何使用.

--- 常量
除了变量和数字以外,QB还支持常量,用CONST表示:
CONST PI#=3.14159265358979323846
以后就可以用pi#表示这么长串数字了,但不能赋值。
CONST后面的表达式可以是四则运算,但不能有函数运算,比如:
CONST PI2#=PI#*2 'PI#也是常数,所以能在这里使用

讲了这么多,你现在真正能看到效果的只有前面那个画图的程序。
好象很无聊,打基础嘛,我已经尽力使之做到“有聊”了。 :)
因为这些你现在用不着,以后就很常用了,也不用硬记,用的时
候可以把这封再复习一遍。

啊~~好累,今天到这里,就到这里吧。 :)

上一次由areyong于2005-3-28 周一, 下午12:11修改,总共修改了1次

返回顶端


areyong
论坛管理员




加入时间: 2004/11/09
文章: 374
来自: 北京
Points: 556

时间: 2004-12-03 周五, 下午5:34 标题:

--------------------------------------------------------------------------------

在写教材时发现大家都喜欢实例教学,似乎直接把我的例子抄过
去学得最快最轻松.
因此我就多多举例. 不过读者最好购买一本QB的书,看附录
部分有详细的命令和函数的列表的那种就成,因为光举例子不可能
覆盖全面.如果你的英文还可以,也可以看QB的HELP,非常详细,
而且几乎每个例子都有一个example.

第一节其实还少说了点东西, 因为我太困忘了写. :)
--- 自定义数据类型:
象C语言的struct结构,QB也能用TYPE命令自己创造数据类型。如:

TYPE student '定义一个“学生”类型
sname AS STRING*8 'NAME是保留字不能用
age AS INTEGER
score as integer
END TYPE

在TYPE定义里不能有不定长字符串,只能用定长字符串。
TYPE的作用正是处理数据库!看上面这段定义象不象数据库的字段名?
如何打开和处理库文件我们以后再说。
定义完怎样使用呢?用DIM定义变量,用 变量名.字段名 引用:
DIM stu as student
stu.sname="小芳"

下面看一些例子:
例 2-1
--- ====================================
DEFINT A-Z
TYPE score
sname AS STRING * 8
chinese AS SINGLE
math AS SINGLE
english AS SINGLE
END TYPE

DIM a(10) AS score
i = 0
DO 'DO循环开始
INPUT "姓名"; a(i).sname
INPUT "分数:", a(i).chinese, a(i).math, a(i).english
i = i + 1
LOOP UNTIL LTRIM$(a(i - 1).sname) = ""
'>LTRIM$函数是去掉字符串的前(LEFT)空格,因为sname是定长字符串,输入空
'>字符串也有8个空格. LOOP UNTIL表示循环直到后面表达式的值为"真".
FOR j = i - 2 TO 0 STEP -1 'FOR循环从I-2到0,"步长"每次减一.
PRINT j; ":"; a(j).sname, a(j).chinese, a(j).math, a(j).english
NEXT
--- ====================================
按F5运行后先输入姓名,再输入分数,三个分数用逗号分割,必须写
三个数或输入空的逗号,否则会出错继续让你重新输入.
当不输入名字直接回车则停止,但还要输入两个逗号.最后按反序在屏幕上输出.


你会发现第一个INPUT自动加问号而第二个没有,因为第一个INPUT后跟一个分号,


而第二个后跟逗号.
※再看PRINT命令,PRINT命令也可用问号代替.在变量J和";",a(j).sname间
用分号连接,打印出来的内容就是紧挨着的,而后面用逗号连接,
QB自动按制表位置放置,每隔八个字符才打印一组数字.
而在每个PRINT命令行的最后,如果没有任何符号就会自动换行,
你甚至可以用一个空的PRINT表示多换一行。如果后面有分号或逗号,
那会影响到下一个PRINT命令的位置。

--- ☆ 表达式
QB的数学运算符有: 加+,减- ,乘* ,除 /,整除 \(截断小数部分),
取模 MOD, 乘方^.
比如上次我们那个画图的程序,循环由0到639,可颜色数只有16种(0-15)
因此我们用MOD运算.所谓取模就是求余数.
"怎么没有开方呀?"
有啊, X^.5 不就是对X开方么?(X的0.5次方)
怎样取倒数取平方也知道了吧,不过乘方的速度很慢,二次方最好用
X*X,取倒数最好用 1/x。
此外还有函数,跟数学上的函数概念差不多,比如在立即窗里输入:
? SIN(30)
哎?怎么不对呀?哦,原来用的是弧度,应该是
? sin(3.141592653589793*30/180) 'PI我背过50位呢。 ;)
运算优先级跟数学一样,括号优先,先函数,再乘方,先乘除,
然后MOD,再加减

QB里很重要的一种运算是逻辑运算。
1.关系运算:
= 等于,当在赋值表达式里“=”表示赋值,但在表达
式里代表判断是否等于,等于则为真(-1),否则为假(0)
> 大于
< 小于
>= 大于等于
<= 小于等于
<> 不等于
关系运算可以比较数字或字符串。
字符串以ASCII值比较大小。
还记得上一篇所说的QB没有逻辑类型么? 试试
? 1=3
有什么结果. 你可以
bool=1=2
结果bool得-1, 而bool可以是int,long甚至double类型

2.逻辑运算符
在QB里,逻辑运算符实际上是对变量逐位进行二进制运算的,有逻
辑运算和逻辑判断双重的作用。
逻辑操作符在数学上称做"布尔运算符"(boolean operator)
QB的HELP 里有关于它们的“真值表”

NOT 取非
AND 与
OR 或
XOR 异或
EQV 等价(同或)
IMP 蕴涵
前三个我就不说了,顾名思义么。
如果c=a XOR b,则当a=b时c得0,不相等为1
如果c=a EQV b,则..........1,........0
....... IMP ......a=1,b=0时c=0,否则c=1
如果A,B每个二进制位都是0或1,即A和B都为真(-1)或假(0),
那它的值必然也是-1(真)或0(假). 如果不为0或-1这两个数
则要用二进制一位一位的算(所有逻辑运算都是这样)
比如 4(100) xor 5(101) =1(001) (括号里是二进制)
QB没有逻辑类型,我们通常用这个方法定真假:
CONST False=0
CONST True=NOT false '不假可不就是真么。 ;)
只有关系运算才返回的肯定是0或-1,其他逻辑运算除了值是0或-1,否则
一般都不是返回0、-1,而是按位运算的.

下面这是个把十进制数字转换成二进制数字的程序:
例 2-2
--- ====================================
DEFINT A-Z
INPUT a
FOR i = 15 TO 0 STEP -1 'INT是16位整数,二进制高位在前,因此倒序循环16


IF a AND 2 ^ i THEN '2的i次方就是二进制数第i位等于1的情况
PRINT "1";
ELSE
PRINT "0";
END IF
NEXT '下一次循环
PRINT '多换一行,否则下次运行时不好看,不信你把这行去掉试试。
--- ====================================
上面用了分行的IF命令,格式是
IF 表达式 THEN
当为真(非0)时许多许多命令
ELSE
当为假(等于0)时许多许多命令
END IF
也就是说IF a AND 2 ^ i THEN...和IF NOT((a AND 2 ^ i)=0) THEN
是等价的。
我们可以反复嵌套,比如
IF .... THEN
IF ... THEN
IF....
......
END IF
END IF
END IF
注意IF和END IF必须配对,否则会报错,因此最好象我这样用缩进格式。
包括循环语句也是,循环也可嵌套,为保证配对也最好用缩进格式。
Q> 这些都是你自已想出来的还是书上说的,你给
Q> 记住了.要是我自已想,哪想的出来啊. :(((
Q> 这是不是就叫算法?我现在也没完全看明白.
Q> 这可怎么学呀,一个一个的背?不是办法吧? :(((
A>当然是我自己编的了.我的教程里没有完全抄袭的程序,最多抄抄公式
A>和算法. :)
A>可以说这就是算法吧,编程需要较好的数学能力.比如从10进制转成二进制
A>的公式是用那个数反复除以2,把余数倒过来写,但这种方法只适用于手工计算.


A>二进制的特点是第0位表示0或1,第1位表示0或2,第三位表示0或4...第N位
A>表示0或2的N次方.
A>2^i正是代表要查的位,比如2^3=8,二进制是1000,AND是位运算,只有两边都是1


A>结果才也是1,比如二进制 10100 and 1000 =0 说明第三位是0,
A>如果11010 AND 1000 = 1000 不等于0 说明第三位是1.

在这里提一个系统函数:COMMAND$,没有任何参数。
它是命令行参数字符串。假如把上面这个程序的INPUT A换成
a=VAL(COMMAND$),VAL函数是把字符串转换成数字。
然后编译成EXE文件,假如叫做bin.exe,运行时打:
bin 2334 (回车)
你就能看到结果了,好象DOS命令一样,COOL吧。 :)
当然,不编译成EXE也可以看到效果的,在RUN菜单有一项可以修改COMMAND$
的值。

字符串运算只有加法,就是简单的把两个字符串连接在一起。
但QB的字符串函数非常丰富.比如下面这些常用函数:
下面都用实例来描述,直接在立即窗里输入就能看到效果.

VAL:
s$="3.1e-2": ? VAL(s$)
把S$字符串转换成数字变量.输出结果: .032

STR$:
?STR$(23)
把数字转换成字符串.是VAL的反函数.

CHR$
?chr$(7)
把数字转成相应的ASCII字符.

ASC()
?asc("a")
把字符转成ASCII码,是CHR$的反函数

LEN
LEN("12345")
返回字符长度。其实这个LEN可以返回任何变量的长度,包括自定义的TYPE。
在数据库读写中非常有用。

INSTR
?INSTR(1,"Ms Qbasic","basic")
搜索字符串,第一个数字是起始位置,可以省略。
如果返回0则是没找到。

UCASE$
?UCASE$("DdFd")
全改大写字母

LCASE$全改小写
LTRIM$去前导空格,RTRIM$去后面的空格。前面在例 2-1里已经说过。

HEX$是把十进制数转换成16进制字符串,比如 ?HEX$(255) 显示 FF
OCT$是................8 ..........
BIN$...没有这个函数啦,要你自己来编才成. ;)


发现了么?凡是反回字符串的函数总有个$,返回数字的就没有.
以后我们还能自己编函数和命令呢.

-3-
有了上面那些枯燥无味的基础,这一章我们就可以讨论点具体的了。

下面这个程序是个秒表,按任意键结束。
例 2-3
--- ====================================
t1 = TIMER
CLS '清屏
COLOR 14, 1 '设置字符色为14(黄色),背景是1(兰色)
WHILE (INKEY$ = "")
LOCATE 10, 20 '把光标移到 第10行,第20个字符的位置
t2 = TIMER
IF t2 < t1 THEN t1 = t1 - 24 * 60 * 60
PRINT t2 - t1
WEND
--- ============
TIMER函数是从午夜零点到现在的秒数,用来计时。
好象只要把当前时间与开始时间相减就成了,可是假如计时中
到了零点(咱们网友的习惯)怎么办?
IF语句判断是否过0点,如果过了就把开始时间减去一整天的时间(秒)。
这里的IF语句用的是单行IF命令。格式是:
IF 表达式 THEN 为真时命令 ELSE 为假时命令
其中的命令可以用冒号分隔使用多个命令。

INKEY$函数是我最喜欢的输入命令,它运行时读键盘缓冲区,如果没有字符
则返回空( "" 或者CHR$(0) ),有则返回字符.
如果是字母/数字返回的是ASCII码(可以查QB的HELP的CONTANTS有ASCII表)
需要知道的是,ESC是ASCII 27,BACKSPACE是8,ENTER是13,TAB是9,
如果PRINT CHR$(7)就会"滴"的一声响.
如果是控制键(比如光标键,PAGEUP等),返回的是两个字符长的字符串.
第一个是空(CHR$(0) ),第二个是IBM键盘扩展ASCII码,在HELP的
CONTANTS部分有KEYBOARD SCAN CODE表,键盘扫描码基本跟扩展ASC码一样.
比如你想判断输入的字符是否是键: IF INKEY$=CHR$(0)+CHR$(72) THEN...
很遗憾的是键盘扫描码不等于扩展ASCII码,QB里也没有,我的书里有.
如果你不知道可以编个程序一个一个实验各个键的码.
可以看出,INKEY$执行时不等待直接返回的这种性质可以编出象绘图,
动画控制,游戏等即时控制程序,非常有用.

关于LOCATE命令的格式是
LOCATE 行,列,光标是否可见,光标大小起始行,光标大小结束行
后面的都可以省略,尤其光标大小,很多显示卡都不支持。
而COLOR命令本还有一个参数是设置边框色,但QB有BUG,其实也不支持。
具体颜色代码请读者自己编个程序查查看,用两个FOR循环就成了
范围0-15

Q> t1 = t1 - 24 * 60 * 60
Q> 还有这句,我凭自已想很难想出来这么写.
Q> 是不是只能凭经验了?
A>当然不是,要经过你的思考.如何把生活中的事物表达成函数式也是一种能力.


A>就好象你把物理的应用问题转换成数学方程一样.
A>上面这个数是这样算出来的:
A>一天24小时,乘以一小时60分钟,乘以一分钟60秒. :)
A>有空给你看一个"万年历"的程序,非常著名.它的难点是把格里历转换
A>成公式,用BASIC居然一行就能完成了.
A>我以前编过一个叫BT(BatTool)的小软件, 里面就附带这个万年历程序.

下面就介绍一个比较实用的程序 - 破除SHAREWARE时间限制。
以前ZHANG TING前辈介绍过用QBASIC绕过CCDOS97时间限制的
方法。
实现的方法很多,下面是其中一种,可能与ZT的有点不同:
例2-4
--- ===========SAVEDATE.BAS ============
PRINT "DATE ";
PRINT DATE$
PRINT "DEL UPDATE.BAT"
DATE$="06-01-1997" '假设初次安装日期是97年6月1日
SYSTEM
--- ============CC.BAT==================
QBASIC /RUN SAVEDATE.BAS > UPDATE.BAT
CCDOS
rem 这里放输入法, 比如TWABC
UPDATE
--- ====================================
实际运行时运行CC.BAT就成了
BTW:此程序我未试过,我支持国产正版软件,至今仍用东方快车版。 :)
我想运行完应该给一个Batch file missing或者“批处理文件丢失”
的错误信息,但无关紧要。
上面这个例子介绍了DATE$函数和DATE$命令(名字都一样),类似的
还有TIME$函数和命令。
DATE$是个"违反常规"的函数,既可以取出日期,也可赋值修改日期.
DATE$="日期" 在QB的HELP里规定为"语句"而不是函数,
返回DATE$则规定为"日期函数".
TIME$函数你自己试试就知道了.
SYSTEM命令如果你用QBASIC或QB启动再OPEN程序运行,就跟END命令一样,
如果你在启动时用 QB /RUN XXX.BAS启动会自动运行BAS程序而不进入编辑界面.


而此时SYSTEM则能自动退出而不返回QB的编辑界面.
也就是说用上面的方法你用不着把程序编译成EXE也能执行,这也是
老式BASIC的运行方式.
上面那个程序如果编译成.EXE文件速度会快一些, 而且屏幕不会闪一下.

DOS命令QBASIC虽然只有QB的部分功能,但也能做不少事。
比如我的BWTE的前身就是用QBASIC+批处理的办法。


--- 现在复习一下分支语句。
QB的分支语句有IF,ON... GOTO和SELECT三种,最简单的IF语句是
IF 表达式 THEN 行号
行号有两种,一种是数字,比如

x=1:y=1
IF x=y then 20
END
20 ?20

但这个数字的大小是没有意义的,跟GWBASIC不同。
QB推荐使用字母行标号,与其他语言一样,比如

x=1:y=1
IF x=y then GOTO Label
END
Label: ? 20

不过GOTO命令最好不要单独使用,结构化编程不使用GOTO
连上面那样的转向语句最好也不用,直接用分行IF...END IF最好。
既然GOTO不好,ON...GOTO也就不说啦. :)
现在只有SELECT没有讲过了.看下面这个例子,可以实现菜单操作:
例2-5
--- ================
?"========================"
?" 0,E.什么都不干"
?" 1.列目录"
?" 2.改名"
?" 3,4,5.删除"
?"========================"
DO
A$=INPUT$(1) '从键盘读一个字符. 所谓"读"对于计算机来说就是输入

啦.
SELECT CASE A$
CASE "0","E"
END
CASE "1"
SHELL "DIR /P"
CASE "2"
NAME "C:\AUTOEXEC.BAT" AS "A.BAK"
CASE "3" TO "5"
KILL "C:\COMMAND.COM"
CASE IS > "Z"
?"输了个小写字母?"
CASE ELSE
?"输入错误"
END SELECT
LOOP '没有任何条件参数的DO...LOOP循环是死循环.
--- ================
SELECT命令的条件除了可以用取值列表、取值范围外,还可以用IS变量,
IS代表初始条件值,就是A$啦。
INPUT$函数需要一个整数的参数,表示输入几个字符.
它直接读指定个数字的符,而且不在屏幕上显示.
END命令是结束命令,但可以省略,这样程序就由第一行运行到最后一行.
类似SYSTEM命令, 但不会自动退出.
SHELL命令是调用DOS命令. 如果没有参数就可以进入DOS方式,但QB程序
仍然在内存里,比如你在立即窗里输入SHELL,然后打MEM/C/P看看内存还剩多少.


返回是用DOS命令EXIT.如果有参数,当运行完参数里的程序后将会自动
返回.
NAME...AS命令相当于DOS命令REN,不过你可不要真的把AUTOEXEC.BAT给改没了.

;)
KILL命令相当于DOS命令DEL. 哦,对了,仔细看看这个程序再运行,
可不要运行完了就启动不了计算机了. ;)

--- 最后,再总结一下QB的输入命令:
INPUT命令,想想分号和逗号的区别。如果INPUT后跟很多变量,
必须用逗号分隔每个变量。而且如果输入的是字符串,不能有逗号。
所以如果输入一行东西不如使用LINE INPUT。比如:
LINE INPUT A$
你可以输入任何字符,直到输入回车为止。当然CTRL-C和CTRL-BREAK不成。
a$=INPUT$(n) 函数是从键盘缓冲区里直接读n个字符,送到a$里,
如果A$=INPUT$(1)就相当于PRESS ANY KEY TO CONTINUE...了。
( Which key is "ANY KEY"? ;) )
INKEY$函数每次都不等待直接从键盘缓冲区里读一个键,而且可以是
光标控制键。如果用户没有按键直接返回空字符。
无论是哪一种输入方法,QB都可能有汉字不能输入,比如“金”,
这是QB的BUG,如果你用的是MSBASIC 7就能解决问题了。

上面这些囫囵吞枣的灌给大家,恐怕记不住多少,应该
把所有例子都运行一遍,有点印象就好,编的时候再查书。
下次我就讲最吸引人的图形&动画. :)


... -> ==>> ___whc.yeah.net____\ ┬── ┬─┐
~~~ -> ==>> ___________________ `> ├─ ├─┘
..> -> ==>> whc@nease.net / ┴  ┴ 

... press ALT-A key to continue...
--- GoldED/386
! Origin: Beijing West-Point Porgrammer's BBS * 010-65233230 (6:650/2

7)


--------
信件: 2333 日期: 26 Oct 98
11:30:44
来自: Float Pointer 已读: 是 已回
信 : 否
给: All 标记:

主题: FP的QB教程 第二篇
----------------------------------------------------------------------
--------

在写教材时发现大家都喜欢实例教学,似乎直接把我的例子抄过
去学得最快最轻松.
因此我就多多举例. 不过读者最好购买一本QB的书,看附录
部分有详细的命令和函数的列表的那种就成,因为光举例子不可能
覆盖全面.如果你的英文还可以,也可以看QB的HELP,非常详细,
而且几乎每个例子都有一个example.

第一节其实还少说了点东西, 因为我太困忘了写. :)
--- 自定义数据类型:
象C语言的struct结构,QB也能用TYPE命令自己创造数据类型。如:

TYPE student '定义一个“学生”类型
sname AS STRING*8 'NAME是保留字不能用
age AS INTEGER
score as integer
END TYPE

在TYPE定义里不能有不定长字符串,只能用定长字符串。
TYPE的作用正是处理数据库!看上面这段定义象不象数据库的字段名?
如何打开和处理库文件我们以后再说。
定义完怎样使用呢?用DIM定义变量,用 变量名.字段名 引用:
DIM stu as student
stu.sname="小芳"

下面看一些例子:
例 2-1
--- ====================================
DEFINT A-Z
TYPE score
sname AS STRING * 8
chinese AS SINGLE
math AS SINGLE
english AS SINGLE
END TYPE

DIM a(10) AS score
i = 0
DO 'DO循环开始
INPUT "姓名"; a(i).sname
INPUT "分数:", a(i).chinese, a(i).math, a(i).english
i = i + 1
LOOP UNTIL LTRIM$(a(i - 1).sname) = ""
'>LTRIM$函数是去掉字符串的前(LEFT)空格,因为sname是定长字符串,输入空
'>字符串也有8个空格. LOOP UNTIL表示循环直到后面表达式的值为"真".
FOR j = i - 2 TO 0 STEP -1 'FOR循环从I-2到0,"步长"每次减一.
PRINT j; ":"; a(j).sname, a(j).chinese, a(j).math, a(j).english
NEXT
--- ====================================
按F5运行后先输入姓名,再输入分数,三个分数用逗号分割,必须写
三个数或输入空的逗号,否则会出错继续让你重新输入.
当不输入名字直接回车则停止,但还要输入两个逗号.最后按反序在屏幕上输出.

你会发现第一个INPUT自动加问号而第二个没有,因为第一个INPUT后跟一个分号,

而第二个后跟逗号.
※再看PRINT命令,PRINT命令也可用问号代替.在变量J和";",a(j).sname间
用分号连接,打印出来的内容就是紧挨着的,而后面用逗号连接,
QB自动按制表位置放置,每隔八个字符才打印一组数字.
而在每个PRINT命令行的最后,如果没有任何符号就会自动换行,
你甚至可以用一个空的PRINT表示多换一行。如果后面有分号或逗号,
那会影响到下一个PRINT命令的位置。

--- ☆ 表达式
QB的数学运算符有: 加+,减- ,乘* ,除 /,整除 \(截断小数部分),
取模 MOD, 乘方^.
比如上次我们那个画图的程序,循环由0到639,可颜色数只有16种(0-15)
因此我们用MOD运算.所谓取模就是求余数.
"怎么没有开方呀?"
有啊, X^.5 不就是对X开方么?(X的0.5次方)
怎样取倒数取平方也知道了吧,不过乘方的速度很慢,二次方最好用
X*X,取倒数最好用 1/x。
此外还有函数,跟数学上的函数概念差不多,比如在立即窗里输入:
? SIN(30)
哎?怎么不对呀?哦,原来用的是弧度,应该是
? sin(3.141592653589793*30/180) 'PI我背过50位呢。 ;)
运算优先级跟数学一样,括号优先,先函数,再乘方,先乘除,
然后MOD,再加减

QB里很重要的一种运算是逻辑运算。
1.关系运算:
= 等于,当在赋值表达式里“=”表示赋值,但在表达
式里代表判断是否等于,等于则为真(-1),否则为假(0)
> 大于
< 小于
>= 大于等于
<= 小于等于
<> 不等于
关系运算可以比较数字或字符串。
字符串以ASCII值比较大小。
还记得上一篇所说的QB没有逻辑类型么? 试试
? 1=3
有什么结果. 你可以
bool=1=2
结果bool得-1, 而bool可以是int,long甚至double类型

2.逻辑运算符
在QB里,逻辑运算符实际上是对变量逐位进行二进制运算的,有逻
辑运算和逻辑判断双重的作用。
逻辑操作符在数学上称做"布尔运算符"(boolean operator)
QB的HELP 里有关于它们的“真值表”

NOT 取非
AND 与
OR 或
XOR 异或
EQV 等价(同或)
IMP 蕴涵
前三个我就不说了,顾名思义么。
如果c=a XOR b,则当a=b时c得0,不相等为1
如果c=a EQV b,则..........1,........0
....... IMP ......a=1,b=0时c=0,否则c=1
如果A,B每个二进制位都是0或1,即A和B都为真(-1)或假(0),
那它的值必然也是-1(真)或0(假). 如果不为0或-1这两个数
则要用二进制一位一位的算(所有逻辑运算都是这样)
比如 4(100) xor 5(101) =1(001) (括号里是二进制)
QB没有逻辑类型,我们通常用这个方法定真假:
CONST False=0
CONST True=NOT false '不假可不就是真么。 ;)
只有关系运算才返回的肯定是0或-1,其他逻辑运算除了值是0或-1,否则
一般都不是返回0、-1,而是按位运算的.

下面这是个把十进制数字转换成二进制数字的程序:
例 2-2
--- ====================================
DEFINT A-Z
INPUT a
FOR i = 15 TO 0 STEP -1 'INT是16位整数,二进制高位在前,因此倒序循环16

IF a AND 2 ^ i THEN '2的i次方就是二进制数第i位等于1的情况
PRINT "1";
ELSE
PRINT "0";
END IF
NEXT '下一次循环
PRINT '多换一行,否则下次运行时不好看,不信你把这行去掉试试。
--- ====================================
上面用了分行的IF命令,格式是
IF 表达式 THEN
当为真(非0)时许多许多命令
ELSE
当为假(等于0)时许多许多命令
END IF
也就是说IF a AND 2 ^ i THEN...和IF NOT((a AND 2 ^ i)=0) THEN
是等价的。
我们可以反复嵌套,比如
IF .... THEN
IF ... THEN
IF....
......
END IF
END IF
END IF
注意IF和END IF必须配对,否则会报错,因此最好象我这样用缩进格式。
包括循环语句也是,循环也可嵌套,为保证配对也最好用缩进格式。
Q> 这些都是你自已想出来的还是书上说的,你给
Q> 记住了.要是我自已想,哪想的出来啊. :(((
Q> 这是不是就叫算法?我现在也没完全看明白.
Q> 这可怎么学呀,一个一个的背?不是办法吧? :(((
A>当然是我自己编的了.我的教程里没有完全抄袭的程序,最多抄抄公式
A>和算法. :)
A>可以说这就是算法吧,编程需要较好的数学能力.比如从10进制转成二进制
A>的公式是用那个数反复除以2,把余数倒过来写,但这种方法只适用于手工计算.

A>二进制的特点是第0位表示0或1,第1位表示0或2,第三位表示0或4...第N位
A>表示0或2的N次方.
A>2^i正是代表要查的位,比如2^3=8,二进制是1000,AND是位运算,只有两边都是1

A>结果才也是1,比如二进制 10100 and 1000 =0 说明第三位是0,
A>如果11010 AND 1000 = 1000 不等于0 说明第三位是1.

在这里提一个系统函数:COMMAND$,没有任何参数。
它是命令行参数字符串。假如把上面这个程序的INPUT A换成
a=VAL(COMMAND$),VAL函数是把字符串转换成数字。
然后编译成EXE文件,假如叫做bin.exe,运行时打:
bin 2334 (回车)
你就能看到结果了,好象DOS命令一样,COOL吧。 :)
当然,不编译成EXE也可以看到效果的,在RUN菜单有一项可以修改COMMAND$
的值。

字符串运算只有加法,就是简单的把两个字符串连接在一起。
但QB的字符串函数非常丰富.比如下面这些常用函数:
下面都用实例来描述,直接在立即窗里输入就能看到效果.

VAL:
s$="3.1e-2": ? VAL(s$)
把S$字符串转换成数字变量.输出结果: .032

STR$:
?STR$(23)
把数字转换成字符串.是VAL的反函数.

CHR$
?chr$(7)
把数字转成相应的ASCII字符.

ASC()
?asc("a")
把字符转成ASCII码,是CHR$的反函数

LEN
LEN("12345")
返回字符长度。其实这个LEN可以返回任何变量的长度,包括自定义的TYPE。
在数据库读写中非常有用。

INSTR
?INSTR(1,"Ms Qbasic","basic")
搜索字符串,第一个数字是起始位置,可以省略。
如果返回0则是没找到。

UCASE$
?UCASE$("DdFd")
全改大写字母

LCASE$全改小写
LTRIM$去前导空格,RTRIM$去后面的空格。前面在例 2-1里已经说过。

HEX$是把十进制数转换成16进制字符串,比如 ?HEX$(255) 显示 FF
OCT$是................8 ..........
BIN$...没有这个函数啦,要你自己来编才成. ;)


发现了么?凡是反回字符串的函数总有个$,返回数字的就没有.
以后我们还能自己编函数和命令呢.

-3-
有了上面那些枯燥无味的基础,这一章我们就可以讨论点具体的了。

下面这个程序是个秒表,按任意键结束。
例 2-3
--- ====================================
t1 = TIMER
CLS '清屏
COLOR 14, 1 '设置字符色为14(黄色),背景是1(兰色)
WHILE (INKEY$ = "")
LOCATE 10, 20 '把光标移到 第10行,第20个字符的位置
t2 = TIMER
IF t2 < t1 THEN t1 = t1 - 24 * 60 * 60
PRINT t2 - t1
WEND
--- ============
TIMER函数是从午夜零点到现在的秒数,用来计时。
好象只要把当前时间与开始时间相减就成了,可是假如计时中
到了零点(咱们网友的习惯)怎么办?
IF语句判断是否过0点,如果过了就把开始时间减去一整天的时间(秒)。
这里的IF语句用的是单行IF命令。格式是:
IF 表达式 THEN 为真时命令 ELSE 为假时命令
其中的命令可以用冒号分隔使用多个命令。

INKEY$函数是我最喜欢的输入命令,它运行时读键盘缓冲区,如果没有字符
则返回空( "" 或者CHR$(0) ),有则返回字符.
如果是字母/数字返回的是ASCII码(可以查QB的HELP的CONTANTS有ASCII表)
需要知道的是,ESC是ASCII 27,BACKSPACE是8,ENTER是13,TAB是9,
如果PRINT CHR$(7)就会"滴"的一声响.
如果是控制键(比如光标键,PAGEUP等),返回的是两个字符长的字符串.
第一个是空(CHR$(0) ),第二个是IBM键盘扩展ASCII码,在HELP的
CONTANTS部分有KEYBOARD SCAN CODE表,键盘扫描码基本跟扩展ASC码一样.
比如你想判断输入的字符是否是键: IF INKEY$=CHR$(0)+CHR$(72) THEN...
很遗憾的是键盘扫描码不等于扩展ASCII码,QB里也没有,我的书里有.
如果你不知道可以编个程序一个一个实验各个键的码.
可以看出,INKEY$执行时不等待直接返回的这种性质可以编出象绘图,
动画控制,游戏等即时控制程序,非常有用.

关于LOCATE命令的格式是
LOCATE 行,列,光标是否可见,光标大小起始行,光标大小结束行
后面的都可以省略,尤其光标大小,很多显示卡都不支持。
而COLOR命令本还有一个参数是设置边框色,但QB有BUG,其实也不支持。
具体颜色代码请读者自己编个程序查查看,用两个FOR循环就成了
范围0-15

Q> t1 = t1 - 24 * 60 * 60
Q> 还有这句,我凭自已想很难想出来这么写.
Q> 是不是只能凭经验了?
A>当然不是,要经过你的思考.如何把生活中的事物表达成函数式也是一种能力.

A>就好象你把物理的应用问题转换成数学方程一样.
A>上面这个数是这样算出来的:
A>一天24小时,乘以一小时60分钟,乘以一分钟60秒. :)
A>有空给你看一个"万年历"的程序,非常著名.它的难点是把格里历转换
A>成公式,用BASIC居然一行就能完成了.
A>我以前编过一个叫BT(BatTool)的小软件, 里面就附带这个万年历程序.

下面就介绍一个比较实用的程序 - 破除SHAREWARE时间限制。
以前ZHANG TING前辈介绍过用QBASIC绕过CCDOS97时间限制的
方法。
实现的方法很多,下面是其中一种,可能与ZT的有点不同:
例2-4
--- ===========SAVEDATE.BAS ============
PRINT "DATE ";
PRINT DATE$
PRINT "DEL UPDATE.BAT"
DATE$="06-01-1997" '假设初次安装日期是97年6月1日
SYSTEM
--- ============CC.BAT==================
QBASIC /RUN SAVEDATE.BAS > UPDATE.BAT
CCDOS
rem 这里放输入法, 比如TWABC
UPDATE
--- ====================================
实际运行时运行CC.BAT就成了
BTW:此程序我未试过,我支持国产正版软件,至今仍用东方快车版。 :)
我想运行完应该给一个Batch file missing或者“批处理文件丢失”
的错误信息,但无关紧要。
上面这个例子介绍了DATE$函数和DATE$命令(名字都一样),类似的
还有TIME$函数和命令。
DATE$是个"违反常规"的函数,既可以取出日期,也可赋值修改日期.
DATE$="日期" 在QB的HELP里规定为"语句"而不是函数,
返回DATE$则规定为"日期函数".
TIME$函数你自己试试就知道了.
SYSTEM命令如果你用QBASIC或QB启动再OPEN程序运行,就跟END命令一样,
如果你在启动时用 QB /RUN XXX.BAS启动会自动运行BAS程序而不进入编辑界面.

而此时SYSTEM则能自动退出而不返回QB的编辑界面.
也就是说用上面的方法你用不着把程序编译成EXE也能执行,这也是
老式BASIC的运行方式.
上面那个程序如果编译成.EXE文件速度会快一些, 而且屏幕不会闪一下.

DOS命令QBASIC虽然只有QB的部分功能,但也能做不少事。
比如我的BWTE的前身就是用QBASIC+批处理的办法。


--- 现在复习一下分支语句。
QB的分支语句有IF,ON... GOTO和SELECT三种,最简单的IF语句是
IF 表达式 THEN 行号
行号有两种,一种是数字,比如

x=1:y=1
IF x=y then 20
END
20 ?20

但这个数字的大小是没有意义的,跟GWBASIC不同。
QB推荐使用字母行标号,与其他语言一样,比如

x=1:y=1
IF x=y then GOTO Label
END
Label: ? 20

不过GOTO命令最好不要单独使用,结构化编程不使用GOTO
连上面那样的转向语句最好也不用,直接用分行IF...END IF最好。
既然GOTO不好,ON...GOTO也就不说啦. :)
现在只有SELECT没有讲过了.看下面这个例子,可以实现菜单操作:
例2-5
--- ================
?"========================"
?" 0,E.什么都不干"
?" 1.列目录"
?" 2.改名"
?" 3,4,5.删除"
?"========================"
DO
A$=INPUT$(1) '从键盘读一个字符. 所谓"读"对于计算机来说就是输入
啦.
SELECT CASE A$
CASE "0","E"
END
CASE "1"
SHELL "DIR /P"
CASE "2"
NAME "C:\AUTOEXEC.BAT" AS "A.BAK"
CASE "3" TO "5"
KILL "C:\COMMAND.COM"
CASE IS > "Z"
?"输了个小写字母?"
CASE ELSE
?"输入错误"
END SELECT
LOOP '没有任何条件参数的DO...LOOP循环是死循环.
--- ================
SELECT命令的条件除了可以用取值列表、取值范围外,还可以用IS变量,
IS代表初始条件值,就是A$啦。
INPUT$函数需要一个整数的参数,表示输入几个字符.
它直接读指定个数字的符,而且不在屏幕上显示.
END命令是结束命令,但可以省略,这样程序就由第一行运行到最后一行.
类似SYSTEM命令, 但不会自动退出.
SHELL命令是调用DOS命令. 如果没有参数就可以进入DOS方式,但QB程序
仍然在内存里,比如你在立即窗里输入SHELL,然后打MEM/C/P看看内存还剩多少.

返回是用DOS命令EXIT.如果有参数,当运行完参数里的程序后将会自动
返回.
NAME...AS命令相当于DOS命令REN,不过你可不要真的把AUTOEXEC.BAT给改没了.
;)
KILL命令相当于DOS命令DEL. 哦,对了,仔细看看这个程序再运行,
可不要运行完了就启动不了计算机了. ;)

--- 最后,再总结一下QB的输入命令:
INPUT命令,想想分号和逗号的区别。如果INPUT后跟很多变量,
必须用逗号分隔每个变量。而且如果输入的是字符串,不能有逗号。
所以如果输入一行东西不如使用LINE INPUT。比如:
LINE INPUT A$
你可以输入任何字符,直到输入回车为止。当然CTRL-C和CTRL-BREAK不成。
a$=INPUT$(n) 函数是从键盘缓冲区里直接读n个字符,送到a$里,
如果A$=INPUT$(1)就相当于PRESS ANY KEY TO CONTINUE...了。
( Which key is "ANY KEY"? ;) )
INKEY$函数每次都不等待直接从键盘缓冲区里读一个键,而且可以是
光标控制键。如果用户没有按键直接返回空字符。
无论是哪一种输入方法,QB都可能有汉字不能输入,比如“金”,
这是QB的BUG,如果你用的是MSBASIC 7就能解决问题了。

上面这些囫囵吞枣的灌给大家,恐怕记不住多少,应该
把所有例子都运行一遍,有点印象就好,编的时候再查书。
下次我就讲最吸引人的图形&动画. :)

返回顶端


areyong
论坛管理员




加入时间: 2004/11/09
文章: 374
来自: 北京
Points: 556

时间: 2004-12-03 周五, 下午5:35 标题:

--------------------------------------------------------------------------------

今天我们讲最吸引人的图形&动画....慢着,先钓钓胃口. ;)


我们先复习一下QB的循环命令:
FOR...NEXT循环是定次数的循环.
FOR 变量=初值 TO 终值 STEP 步长
STEP步长可省略,默认是1,就是每循环一次加一.
循环的结尾用NEXT表示,我们通常用缩进格式防止FOR...NEXT不配对.
比如
FOR I=A TO B
FOR J=C TO I STEP 10
....
NEXT
FOR...
NEXT
NEXT
在QB里也支持象老式BASIC那样的NEXT I,J,K...一次代表多个FOR结束,
但不推荐这样,容易出错.

不定次循环最简单的是WHILE .... WEND,称做"当循环".比如下面是个表:
--- ===============
WHILE INKEY$=""
LOCATE 10,20
? time$
WEND
--- ===============
当INKEY$函数为空(没有按键盘),就继续循环,否则就跳到WEND后面

QB新加的循环是DO...LOOP循环.
如果光是
do
locate 10,20
?time$
loop
那永远也退不出来,只能按CTRL-BREAK终止.

所以要加上终止条件,有WHILE和UNTIL两种,WHILE是当后面的条件为真
循环,UNTIL则正好相反,直到后面为真才退出.
可以加在DO后也可
加在LOOP后.
比如

DO WHILE INKEY$=""
LOCATE 10,20
? time$
LOOP

就跟第一个例子一模一样了.

如果加在LOOP后面有什么区别呢?
看下面这个例子:

I=0
DO
?"进入循环"
LOOP UNTIL I=0

如果你把UNTIL I=0加在DO的后面试试看.
也就是说,LOOP UNTIL(WHILE)无论什么条件都要执行循环体至少一次.

还有一种终止循环的方法,EXIT命令. 在循环体内部的任何地方,
只要用EXIT DO就能退出DO循环,跳到LOOP的后面.
此外也可以用EXIT FOR退出FOR循环.
... 注意没有EXIT WHILE.

呼~该说图形了。
想当初刚学会BASIC,刚用到新PC机,发现居然只能用4种颜色320X200
的分辨率,连苹果机都不如,可明明波斯王子却画得那么漂亮,
.......就不忆苦思甜了。 ;)
就在那个分辨率下,我的BASICA书里居然做出来各种漂亮的二维三维
图形和动画,下面有些例子就是从那里抄的,改成VGA的分辨率。
先说一些计算机图形的知识。 显示卡可以以很多工作方式显示图象,
每一种叫做一种显示模式,有些高手也能自己创造新的显示模式。
每种显示模式有不同的分辨率,发色数,配色数,这我们在用WIN95
时就很清楚了。显示模式主要分为文本模式和图形模式两大类。
我们只介绍VGA兼容模式。QB用SCREEN 命令设置显示模式。
以前我们都用的是文本模式3,80X25字符,16色前/背景。
用QB表示出来就是
SCREEN 0 'QB里称做模式0,是唯一的文本模式
WIDTH 80 '设置宽度为80,也可设置成40。一般可以省略
如果在其他模式画完图不运行上面这两行,退出程序后可能会很不好看。
但一般来说QB会恢复默认模式的.

由于过去的技术落后,发色数和分辨率不能两全。QB也是如此.
本文只介绍QB的三种模式:
SCREEN 9:640X350 X16配色器,64个发色数,双屏幕页,用于屏幕页动画
可用于EGA显示器。
SCREEN 12:640X480 X16配色器,64的三次方发色数,是日本RPG常用的模式。


SCREEN 13:320X200 X256配色器,64的三次方发色数,是C&C的显示模式。 :)


在96年以前,绝大多数游戏都用模式13,系统模式是13H,是用颜色换
分辨率的一种手段,也是“金山影霸”比当时的XING1.3要快的原因。

什么叫做配色器呢?看下面这个程序:

?"TEST"
FOR I=0 TO 63 '文本模式只有64种发色数
PALETTE 7,I
NEXT

大家知道计算机是用显存保存图象、图形信息的,在相同分辨率下,
表示的颜色越多需要的内存也越多。
比如640列真彩色需要640x480x256^3/8字节的内存,是个相当大的数字,
过去的CPU不能承受,因此用配色器的办法,存在显存里的只是颜色属性,
显示卡自动从颜色属性代表的配色器里取出真正的颜色(真彩色),
再显示在屏幕上。
这种方法不仅提高了速度,而且为编程者提供了一种编程方法,只要
改动一个配色器的值就可以把屏幕上所有此属性的象素该成那种颜色,
WIN95的启动画面就是用这种方法做的“动画”。
我们整理一下名词定义:
--- 属性:存在显存里的数值,画图时直接用,比如以前我们见过的
COLOR命令,后面跟的参数就是属性,而不是真正的颜色,真正的颜色
取决于:
--- 配色器:每个属性都对应一个配色器,其多少是由显示模式决定的。
里面保存的是此属性真正的颜色代码。用 PALETTE命令改变。
这里沿用EGA的名称,在VGA硬件里称做DAC寄存器。SVGA通常有24BIT DAC
就是所谓24比特真彩色了。
--- 最大发色数:表示真正的颜色代码的最大值,取决于显示模式。

当然,在SCREEN换模式时系统会自动把各配色器置默认值,所以通常
我们总把属性0当作黑色,其实用PALETTE 0,X可以把背景改成任意
颜色。
我们可以一次改一个配色器的属性:
PALETTE 属性,颜色代码(以后简称为颜色)。
但速度太慢,也可一次把所有配色器都改了:
PALETTE USING 颜色数组
颜色数组通常是一个LONG型的数组,可以有下标,也可以省略。
比如PALETTE USING A&(13)表示从A&(13)开始n个变量表示配色器,
n就是发色数。 也可以用PALETTE USING A&表示从A&(0)开始的
n个配色器。

配色器的颜色代码是这样:

模式0和9都是64最大发色数,用个整数就能表示。它是按二进制数表示的:
R'G'B'R G B 共六位,RGB就是红绿蓝了,加个撇代表亮度高点的RGB。
二进制100100就代表最亮的红色(亮红+红=特亮的红),转成十进制
就是32+4=36,在立即窗里输入PALETTE 0,36就能看到.....
用不带参数的PALETTE可以恢复原来默认的配色器设置。
默认是
0黑 1深蓝 2深绿 3青 4红 5紫红 6棕 7浅灰
8深灰 9蓝 10绿 11浅青 12亮红 13亮紫 14黄 15亮白
发现了么?是按二进制IRGB的规律排列的,I是亮度位。

模式12和模式13是64的三次方种发色数,用三个字节表示,只能用
LONG型变量。计算方法是:
C&=G * &H10000 + B * &H100 + R
注意,RGB的顺序颠倒过来了,是BGR。
R,G,B分别是红绿蓝三个分量的值,范围是0 - 63
这样的颜色数远超过高彩色(65536)了,可惜不能同屏显示出来。
但要显示一张模糊的照片还是可以的。 :)

Q>C&=G * &H10000 + B * &H100 + R
Q>~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Q>这是什么东西,看不明白. ;(
A>十六进制是两个数表示一个字节,比如FF就是255,是一个字节.
A>而FFFF是两个字节,是65535. 这在一些游戏攻略上经常能见到.
A>G是绿色,B是兰色,R是红色,各占一个字节,表示一种颜色.要把它们
A>合在一起,就把第二个字节乘以&h100,第三个乘&h10000,这跟十进制
A>乘法很象的.


关于模式12,在第一部分就讲过了,请读者回去看看,试试能不能
用配色器把那个图形弄得更奇怪一些。 :)

模式13是最有用的模式,因为它的配色器最多,同一屏幕显示的颜色
曾经是最多的,所占显存又远小于模式12,因此适用于此模式的GIF格式
图形曾经特别流行,直到现在真彩色模式的应用才使JPG普及开。
看下面这个程序:
--- ===============
SCREEN 13
DIM pal(511) AS LONG

FOR i = 0 TO 255 STEP 2
LINE (0, i)-(639, i * 2), i / 2, BF
NEXT
FOR i = 0 TO 511
pal(i) = (i MOD 64) * &H10000
NEXT
j = 0
WHILE INKEY$ = ""
PALETTE USING pal(j)
j = (j + 1) MOD 256
WEND
--- ===============
运行时你会发现其速度之慢不可忍受,这是QB的一大BUG,于是我们用下面
这段代码替换PALETTE那行,速度就快多了:
--- ===============
FOR i = 0 TO 255
OUT &H3C8, i + j
OUT &H3C9, pal(i) AND &HFF
OUT &H3C9, (pal(i) AND &HFF00) \ &H100
OUT &H3C9, (pal(i) AND &HFF0000) \ &H10000
NEXT
--- ===============
计算机的各种设备都连在设备总线上,都有一些端口用于操作。
OUT是对计算机的端口进行操作的命令。前面是端口号,后面的是数据。
端口&h3C8和&h3C9是DAC寄存器,&H3c8是索引,指出要操作哪个寄存器,
&h3C9是数据寄存器,第一次是B,第二次G,第三次R。如果用INP函数
可以读出DAC寄存器的值,比如:

OUT &h3C8,1
?"BLUE:";INP(&H3C9)
?"GREEN:";INP(&H3C9)
?"RED:";INP(&H3C9)
可显示属性1的各分量。
上面那段程序仍然不是很快,没办法。以后我们讲混合编程时也许
可以用汇编语言混合编程来解决。事实上,如果要用QB做比较快速
的动画,通常要汇编语言的帮助,但你不并需要学习汇编,有现成的
函数可用。
注意:由于端口号不同,模式9不能使用上面的方法,因为配色器少,
使用PALETTE足够了。
Q>我怎么知道哪个端口是干什么的呢,DAC是什么东西.
Q>这点有点乱.
A>这就有点复杂了,要查资料,否则我也不知道.
A>可以买本硬件的书,但你不懂汇编语言估计看不懂.
A>怎么给你写的你就照抄就完了,不必管它是怎么实现的,把
A>那一大堆语句当一句PALETTE就成了. :)


说了这么半天,还没讲怎么画图呢。别着急,还要先说一下坐标系统:
QB默认以屏幕左上角为0,0点,右下角为最大位置,由分辨率决定,
比如对于分辨率是640X480的模式12就是 (639,479)。
这跟习惯上的笛卡尔坐标系不一样,Y轴方向反了。
如果画图时超过这个限制会自动截断。QB本身提供了一些坐标变换
和放缩的命令转换坐标。看下例:
--- =================
SCREEN 12
'WINDOW SCREEN (-10, 0)-(100, 100)
'VIEW (0, 10)-(100, 100)
LINE (110, 120)-(130, 140), 3, B
LINE (40, 40)-(60, 60), 3, BF
CIRCLE (0, 0), 40, 10
PAINT (0, 0), 14, 10
--- =================
输入后先运行一遍,再把WINDOW前面那个撇去掉试试,再把VIEW前面
哪个撇去掉试试,最后把WINDOW后面那个SCREEN去掉试试
还有可以去掉前面WIDTH那个撇号看看效果。

WINDOW命令是调整屏幕坐标系统用的。如果后面没有跟着SCREEN则会
把后面坐标中小的当成左下角,跟笛卡尔坐标的习惯相同,否则
还跟以前一样,小的是左上角。WINDOW能把显示区域移到物理屏幕外,
并且把逻辑坐标放缩成物理坐标。具体的你可以改改上面那个例子看看。
我的书上是用图来表示的,我没办法给大家画出来。 :(

VIEW命令是设置可见区域,刚才说了,QB把超过屏幕范围的图形自动删掉,
而VIEW可以把超过指定范围的图形删掉,好象在屏幕里做个小屏幕。
VIEW的语法定义是:
VIEW(x1,y1)-(x2,y2),颜色,边框颜色
颜色是在VIEW框内的颜色,边框颜色是框外的颜色。
提醒一下,所谓颜色其实是属性,并非真正的颜色,要看配色器。
没有任何参数的VIEW和WINDOW就能恢复原坐标系统,但画上的图是改不了了。
把WINDOW语句虚拟的坐标系叫做逻辑坐标,系统本身的是物理坐标,用PMAP函
数可以转换逻辑坐标和物理坐标,看HELP吧。 ;)

我想通过上面这个例子,你可以理解怎样用LINE画实心和空心方块了。
画圆是CIRCLE,也可以画弧和椭圆,方法是:
CIRCLE (X,Y),半径,颜色,起始角度,结束角度,纵横比
两个角度都是用弧度表示的,纵横比是画椭圆时用的,可以自己试试看。
由于模式9和13的屏幕纵横比不是4:3,画出来的圆可能是椭圆,如果不指定,
QB会自动调整纵横比,但是画出来的方块可就变成长方形了,需要自己算,
或者用WINDOW命令调整。
Q>想起一个问题,画扇面的程序我在学校编的时侯
Q>不太正常,斜线不是正好连接了两个角,有点歪.是不
Q>是学校显示器的问题?
A>这就是QB画圆命令的"纵横比"的问题,由于你用的显示模式的每个象素不是
A>1:1的,是长的,圆的纵横坐标也不相同.
A>简单的解决的办法是用SCREEN 12.

PAINT是填充图形,PAINT(X,Y),填充色,边框色
如果省略边框色默认是和填充色一样的。QB的填充方法跟PHOTOSHOP可不一样,


它是寻找边框色而不是寻找不同颜色.如果没遇到边框色,会把整个屏幕都充
满,有时遇到一些图形却不能充满,比如两个圆组成的环,你可以试试看。
PAINT还可以填充花纹图形。我觉得实际用途也不大,而且要了解显示屏幕页
面的知识,在M$的手册里讲了4大篇,我就懒得说了。
在QB的范例里有个EDPAT.BAS文件,是示范如何使用花纹填充的。
无论是QB还是MSBASIC,都附带许多示范,虽然做的挺漂亮,但不容易看懂。
你可以运行试试看。
下面是我高中时在GWBASIC上编的一个程序,去了行号,是关于花纹填充的。
--- ================================
DATA 255,75,75,123,255,149,149,221,255,106,106
DATA 239,255,253,253,255,255,38,38,231,255,25
DATA 25,127,255,100,100,124,-1
SCREEN 9
PALETTE 0, 1
w:
READ a
IF a = -1 THEN GOTO E:
a$ = CHR$(a) + a$
i = i + 1
GOTO w:
E:
PAINT (12, 12), a$
--- ================================
由于GWBASIC不是结构化语言,上面这个程序编的不好,用了GOTO语句,
其实完全可以用WHILE代替,读者自己改成WHILE吧。

READ命令是读取DATA语句存放的数据。如果有大量数据一般放到单独的
文件里,但速度较慢。如果少量数据,不如就用DATA命令。
DATA命令后各数据间用逗号分隔,写多少行都可以,QB认为他们都是连
在一起的。DATA可以放到程序中任何位置,QB自动跳过。
DATA命令的存放方式是字符串,即使编译成EXE文件后也能用PCTOOLS等
“看见”文本状态的数据。因此不能用引号,因为引号代表字符串,但
没有引号也代表字符串。由READ命令判断输入数据是数字还是字符串。
如果光输逗号则被认为是空字符串。
READ语句是顺序读取数据的,读到最后如果没有数据则会出错。
READ语句可以同时读取不同类型的几个变量。
可以用RESTORE语句恢复起始位置,从头开始。RESTORE命令
后也可加行号,表示恢复到某一行。比如:

data1:
DATA 2,2
data2:
DATA &HFF,3,a,4,5,b
... ...
RESTORE data2
READ a,b,c$
? a,b,c$

输出结果:
255 3 a
其中前两个是数字,最后一个是字符串。

上面我们解决了二维变换的问题,如果程序需要放大缩小旋转等操作
一般还是用数学方法来解决,这里就不多讨论了。

下面我们讨论三维图形:
--- ===================
SCREEN 12
xu = 350
yu = 80
zu = -30
z = 10 'Z的初值
WIDTH ,30 '你可以把这里换成60试试。模式12的文本模式有80X30和80X60两种


'注意逗号不可少,因为第一个参数是列,第二个才是行.
COLOR 13
LOCATE 27,1
PRINT "3D test"
COLOR 9
DO
p = -zu / (z - zu)
FOR x = 0 TO 1000 STEP 2 '步长值的大小决定画出来的速度和质量,改改看


y = 10 * SIN(.05 * SQR(x * x + z * z))
x1 = xu + (xu - x) * p
y1 = yu + (xu - y) * p
PSET (x1, y1)
NEXT
z = z + 5
LOOP WHILE INKEY$ = ""
--- ===================
三围....咳....三维变换公式是:
p = -zu / (z - zu)
x1 = xu + (xu - x) * p
y1 = yu + (xu - y) * p
其中x,y,z是要显示象素的三维坐标,xu,yu,zu是视角(就是你的眼睛)的坐标,


因为把Z轴方向指向屏幕里,所以你的眼睛的Z坐标是负的。X,Y坐标的指向跟
以前一样。而x1,y1就是投影变换以后的屏幕坐标了。
原理我不说了,通过这个公式可以把空间上一点投影到屏幕上,让眼睛感觉上是


立体的。
y = 10 * SIN(.05 * SQR(x * x + z * z))是一个三维曲线函数,就是你所
看到的那个波浪图。

PSET是画点,后面可以加颜色,比如 PSET(10,10),10
但我省略了,QB按前面COLOR命令设的默认颜色画。实际上所有绘图
命令都可以省略颜色。
在图形模式下打印字跟文本模式一样只是文本分辨率不同。模式9
还是80X25;模式12有两种,一个是80x30一个是80x60,可以用WIDTH
设置;模式13只有40X25,而且返回模式0后仍然保持40X25,比如要用
WIDTH 80改回80X25。打印的颜色取决于COLOR命令
与PSET对应的还有一个PRESET语句,是用来擦点,我觉得没什么用。

我们以前已经见到过COLOR命令了,是在文本模式下。
在模式9里背景颜色是整个屏幕的背景,只要一改整个全改,而不是个别字符。


在模式12和13里根本不能用背景颜色这项,COLOR命令只能有一个参数
就是前景色。
因此如果在上面写字的话文字的背景色一定是黑色的,除非用PALETTE改配色器。



绝对坐标和相对坐标:以前所用的都是绝对坐标,是相对(0,0)的坐标,
QB也可用相对坐标,在每个坐标前加STEP就成了,这时括号里的坐标
是在上一次绘图的坐标基础上移动的数值。

还有个绘图语句是DRAW,它是一种小型的语言,用字符串表示,
有点象LOGO语言。
看下面这段程序:
SCREEN 12
DRAW "bm200,100" '把绘图点移动到200,100,前缀b代表不画
DRAW "c1;s50;" 'c后跟的是颜色,S后是比例,可以改改比例看看。
FOR r = -360 TO 360 STEP 144
DRAW "TA" + STR$(r)
DRAW "r10"
SLEEP
NEXT

SLEEP是按任意键继续的命令,后面可以加个数字,表示等待多少秒后
继续,如果不加就只能永远期待你按键盘了。
关于DRAW的绘图命令请自己看HELP吧,我认为没什么用,很少用过。
比如下面这段程序是我刚翻出来的,是高中时编着玩的:
CONST pi = 3.1415926535#
SCREEN 12
VIEW (1, 1)-(280, 191), , 1
VIEW
PAINT (319, 199), 1, 1
a = 20 - 180
l = 90
x = 90
y = 55
PSET (x, y)
FOR i = 1 TO 9
SLEEP
a = a - 20 + 180
y = y - l * SIN(a * pi / 180)
x = x + l * COS(a * pi / 180)
LINE -(x, y)
NEXT
LOCATE 20: PRINT "Copyright (c) W.H.C": PRINT "corporation"
SLEEP

通过这个九角星的程序能进一步理解VIEW和LINE语句的用法。
LINE语句是可以省略第一个坐标参数的,这时就以上次画的最后
一点为起点开始画。


绘图语句刚说完就费了这么大篇幅,动画和屏幕页都没讲呢。 :(
今天又太累了,不写了。以后我可能还要再写关于鼠标,
EMS内存切换,声卡编程等等比较高级的QB程序编程方法,也许
写VB初步入门,不知道写哪方面的好。

留个"作业"吧,前面有钟表的例子,请用绘图命令编个漂亮点的钟表。
下篇预告:用QB也能做出三维动画?真的!而且动感十足, 没有闪烁!

... -> ==>> ___whc.yeah.net____\ ┬── ┬─┐
~~~ -> ==>> ___________________ `> ├─ ├─┘
..> -> ==>> whc@nease.net / ┴  ┴ 

... Press Alt-A to continue...
--- BBS is a virtual society
! Origin: Beijing West-Point Porgrammer's BBS * 010-65233230 (6:650/2

7)


--------
信件: 2439 日期: 31 Oct 98
16:01:25
来自: Float Pointer 已读: 是 已回
信 : 否
给: All 标记:

主题: FP的QB教程 第三篇
----------------------------------------------------------------------
--------
今天我们讲最吸引人的图形&动画....慢着,先钓钓胃口. ;)


我们先复习一下QB的循环命令:
FOR...NEXT循环是定次数的循环.
FOR 变量=初值 TO 终值 STEP 步长
STEP步长可省略,默认是1,就是每循环一次加一.
循环的结尾用NEXT表示,我们通常用缩进格式防止FOR...NEXT不配对.
比如
FOR I=A TO B
FOR J=C TO I STEP 10
....
NEXT
FOR...
NEXT
NEXT
在QB里也支持象老式BASIC那样的NEXT I,J,K...一次代表多个FOR结束,
但不推荐这样,容易出错.

不定次循环最简单的是WHILE .... WEND,称做"当循环".比如下面是个表:
--- ===============
WHILE INKEY$=""
LOCATE 10,20
? time$
WEND
--- ===============
当INKEY$函数为空(没有按键盘),就继续循环,否则就跳到WEND后面

QB新加的循环是DO...LOOP循环.
如果光是
do
locate 10,20
?time$
loop
那永远也退不出来,只能按CTRL-BREAK终止.

所以要加上终止条件,有WHILE和UNTIL两种,WHILE是当后面的条件为真
循环,UNTIL则正好相反,直到后面为真才退出.
可以加在DO后也可
加在LOOP后.
比如

DO WHILE INKEY$=""
LOCATE 10,20
? time$
LOOP

就跟第一个例子一模一样了.

如果加在LOOP后面有什么区别呢?
看下面这个例子:

I=0
DO
?"进入循环"
LOOP UNTIL I=0

如果你把UNTIL I=0加在DO的后面试试看.
也就是说,LOOP UNTIL(WHILE)无论什么条件都要执行循环体至少一次.

还有一种终止循环的方法,EXIT命令. 在循环体内部的任何地方,
只要用EXIT DO就能退出DO循环,跳到LOOP的后面.
此外也可以用EXIT FOR退出FOR循环.
... 注意没有EXIT WHILE.

呼~该说图形了。
想当初刚学会BASIC,刚用到新PC机,发现居然只能用4种颜色320X200
的分辨率,连苹果机都不如,可明明波斯王子却画得那么漂亮,
.......就不忆苦思甜了。 ;)
就在那个分辨率下,我的BASICA书里居然做出来各种漂亮的二维三维
图形和动画,下面有些例子就是从那里抄的,改成VGA的分辨率。
先说一些计算机图形的知识。 显示卡可以以很多工作方式显示图象,
每一种叫做一种显示模式,有些高手也能自己创造新的显示模式。
每种显示模式有不同的分辨率,发色数,配色数,这我们在用WIN95
时就很清楚了。显示模式主要分为文本模式和图形模式两大类。
我们只介绍VGA兼容模式。QB用SCREEN 命令设置显示模式。
以前我们都用的是文本模式3,80X25字符,16色前/背景。
用QB表示出来就是
SCREEN 0 'QB里称做模式0,是唯一的文本模式
WIDTH 80 '设置宽度为80,也可设置成40。一般可以省略
如果在其他模式画完图不运行上面这两行,退出程序后可能会很不好看。
但一般来说QB会恢复默认模式的.

由于过去的技术落后,发色数和分辨率不能两全。QB也是如此.
本文只介绍QB的三种模式:
SCREEN 9:640X350 X16配色器,64个发色数,双屏幕页,用于屏幕页动画
可用于EGA显示器。
SCREEN 12:640X480 X16配色器,64的三次方发色数,是日本RPG常用的模式。

SCREEN 13:320X200 X256配色器,64的三次方发色数,是C&C的显示模式。 :)

在96年以前,绝大多数游戏都用模式13,系统模式是13H,是用颜色换
分辨率的一种手段,也是“金山影霸”比当时的XING1.3要快的原因。

什么叫做配色器呢?看下面这个程序:

?"TEST"
FOR I=0 TO 63 '文本模式只有64种发色数
PALETTE 7,I
NEXT

大家知道计算机是用显存保存图象、图形信息的,在相同分辨率下,
表示的颜色越多需要的内存也越多。
比如640列真彩色需要640x480x256^3/8字节的内存,是个相当大的数字,
过去的CPU不能承受,因此用配色器的办法,存在显存里的只是颜色属性,
显示卡自动从颜色属性代表的配色器里取出真正的颜色(真彩色),
再显示在屏幕上。
这种方法不仅提高了速度,而且为编程者提供了一种编程方法,只要
改动一个配色器的值就可以把屏幕上所有此属性的象素该成那种颜色,
WIN95的启动画面就是用这种方法做的“动画”。
我们整理一下名词定义:
--- 属性:存在显存里的数值,画图时直接用,比如以前我们见过的
COLOR命令,后面跟的参数就是属性,而不是真正的颜色,真正的颜色
取决于:
--- 配色器:每个属性都对应一个配色器,其多少是由显示模式决定的。
里面保存的是此属性真正的颜色代码。用 PALETTE命令改变。
这里沿用EGA的名称,在VGA硬件里称做DAC寄存器。SVGA通常有24BIT DAC
就是所谓24比特真彩色了。
--- 最大发色数:表示真正的颜色代码的最大值,取决于显示模式。

当然,在SCREEN换模式时系统会自动把各配色器置默认值,所以通常
我们总把属性0当作黑色,其实用PALETTE 0,X可以把背景改成任意
颜色。
我们可以一次改一个配色器的属性:
PALETTE 属性,颜色代码(以后简称为颜色)。
但速度太慢,也可一次把所有配色器都改了:
PALETTE USING 颜色数组
颜色数组通常是一个LONG型的数组,可以有下标,也可以省略。
比如PALETTE USING A&(13)表示从A&(13)开始n个变量表示配色器,
n就是发色数。 也可以用PALETTE USING A&表示从A&(0)开始的
n个配色器。

配色器的颜色代码是这样:

模式0和9都是64最大发色数,用个整数就能表示。它是按二进制数表示的:
R'G'B'R G B 共六位,RGB就是红绿蓝了,加个撇代表亮度高点的RGB。
二进制100100就代表最亮的红色(亮红+红=特亮的红),转成十进制
就是32+4=36,在立即窗里输入PALETTE 0,36就能看到.....
用不带参数的PALETTE可以恢复原来默认的配色器设置。
默认是
0黑 1深蓝 2深绿 3青 4红 5紫红 6棕 7浅灰
8深灰 9蓝 10绿 11浅青 12亮红 13亮紫 14黄 15亮白
发现了么?是按二进制IRGB的规律排列的,I是亮度位。

模式12和模式13是64的三次方种发色数,用三个字节表示,只能用
LONG型变量。计算方法是:
C&=G * &H10000 + B * &H100 + R
注意,RGB的顺序颠倒过来了,是BGR。
R,G,B分别是红绿蓝三个分量的值,范围是0 - 63
这样的颜色数远超过高彩色(65536)了,可惜不能同屏显示出来。
但要显示一张模糊的照片还是可以的。 :)

Q>C&=G * &H10000 + B * &H100 + R
Q>~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Q>这是什么东西,看不明白. ;(
A>十六进制是两个数表示一个字节,比如FF就是255,是一个字节.
A>而FFFF是两个字节,是65535. 这在一些游戏攻略上经常能见到.
A>G是绿色,B是兰色,R是红色,各占一个字节,表示一种颜色.要把它们
A>合在一起,就把第二个字节乘以&h100,第三个乘&h10000,这跟十进制
A>乘法很象的.


关于模式12,在第一部分就讲过了,请读者回去看看,试试能不能
用配色器把那个图形弄得更奇怪一些。 :)

模式13是最有用的模式,因为它的配色器最多,同一屏幕显示的颜色
曾经是最多的,所占显存又远小于模式12,因此适用于此模式的GIF格式
图形曾经特别流行,直到现在真彩色模式的应用才使JPG普及开。
看下面这个程序:
--- ===============
SCREEN 13
DIM pal(511) AS LONG

FOR i = 0 TO 255 STEP 2
LINE (0, i)-(639, i * 2), i / 2, BF
NEXT
FOR i = 0 TO 511
pal(i) = (i MOD 64) * &H10000
NEXT
j = 0
WHILE INKEY$ = ""
PALETTE USING pal(j)
j = (j + 1) MOD 256
WEND
--- ===============
运行时你会发现其速度之慢不可忍受,这是QB的一大BUG,于是我们用下面
这段代码替换PALETTE那行,速度就快多了:
--- ===============
FOR i = 0 TO 255
OUT &H3C8, i + j
OUT &H3C9, pal(i) AND &HFF
OUT &H3C9, (pal(i) AND &HFF00) \ &H100
OUT &H3C9, (pal(i) AND &HFF0000) \ &H10000
NEXT
--- ===============
计算机的各种设备都连在设备总线上,都有一些端口用于操作。
OUT是对计算机的端口进行操作的命令。前面是端口号,后面的是数据。
端口&h3C8和&h3C9是DAC寄存器,&H3c8是索引,指出要操作哪个寄存器,
&h3C9是数据寄存器,第一次是B,第二次G,第三次R。如果用INP函数
可以读出DAC寄存器的值,比如:

OUT &h3C8,1
?"BLUE:";INP(&H3C9)
?"GREEN:";INP(&H3C9)
?"RED:";INP(&H3C9)
可显示属性1的各分量。
上面那段程序仍然不是很快,没办法。以后我们讲混合编程时也许
可以用汇编语言混合编程来解决。事实上,如果要用QB做比较快速
的动画,通常要汇编语言的帮助,但你不并需要学习汇编,有现成的
函数可用。
注意:由于端口号不同,模式9不能使用上面的方法,因为配色器少,
使用PALETTE足够了。
Q>我怎么知道哪个端口是干什么的呢,DAC是什么东西.
Q>这点有点乱.
A>这就有点复杂了,要查资料,否则我也不知道.
A>可以买本硬件的书,但你不懂汇编语言估计看不懂.
A>怎么给你写的你就照抄就完了,不必管它是怎么实现的,把
A>那一大堆语句当一句PALETTE就成了. :)


说了这么半天,还没讲怎么画图呢。别着急,还要先说一下坐标系统:
QB默认以屏幕左上角为0,0点,右下角为最大位置,由分辨率决定,
比如对于分辨率是640X480的模式12就是 (639,479)。
这跟习惯上的笛卡尔坐标系不一样,Y轴方向反了。
如果画图时超过这个限制会自动截断。QB本身提供了一些坐标变换
和放缩的命令转换坐标。看下例:
--- =================
SCREEN 12
'WINDOW SCREEN (-10, 0)-(100, 100)
'VIEW (0, 10)-(100, 100)
LINE (110, 120)-(130, 140), 3, B
LINE (40, 40)-(60, 60), 3, BF
CIRCLE (0, 0), 40, 10
PAINT (0, 0), 14, 10
--- =================
输入后先运行一遍,再把WINDOW前面那个撇去掉试试,再把VIEW前面
哪个撇去掉试试,最后把WINDOW后面那个SCREEN去掉试试
还有可以去掉前面WIDTH那个撇号看看效果。

WINDOW命令是调整屏幕坐标系统用的。如果后面没有跟着SCREEN则会
把后面坐标中小的当成左下角,跟笛卡尔坐标的习惯相同,否则
还跟以前一样,小的是左上角。WINDOW能把显示区域移到物理屏幕外,
并且把逻辑坐标放缩成物理坐标。具体的你可以改改上面那个例子看看。
我的书上是用图来表示的,我没办法给大家画出来。 :(

VIEW命令是设置可见区域,刚才说了,QB把超过屏幕范围的图形自动删掉,
而VIEW可以把超过指定范围的图形删掉,好象在屏幕里做个小屏幕。
VIEW的语法定义是:
VIEW(x1,y1)-(x2,y2),颜色,边框颜色
颜色是在VIEW框内的颜色,边框颜色是框外的颜色。
提醒一下,所谓颜色其实是属性,并非真正的颜色,要看配色器。
没有任何参数的VIEW和WINDOW就能恢复原坐标系统,但画上的图是改不了了。
把WINDOW语句虚拟的坐标系叫做逻辑坐标,系统本身的是物理坐标,用PMAP函
数可以转换逻辑坐标和物理坐标,看HELP吧。 ;)

我想通过上面这个例子,你可以理解怎样用LINE画实心和空心方块了。
画圆是CIRCLE,也可以画弧和椭圆,方法是:
CIRCLE (X,Y),半径,颜色,起始角度,结束角度,纵横比
两个角度都是用弧度表示的,纵横比是画椭圆时用的,可以自己试试看。
由于模式9和13的屏幕纵横比不是4:3,画出来的圆可能是椭圆,如果不指定,
QB会自动调整纵横比,但是画出来的方块可就变成长方形了,需要自己算,
或者用WINDOW命令调整。
Q>想起一个问题,画扇面的程序我在学校编的时侯
Q>不太正常,斜线不是正好连接了两个角,有点歪.是不
Q>是学校显示器的问题?
A>这就是QB画圆命令的"纵横比"的问题,由于你用的显示模式的每个象素不是
A>1:1的,是长的,圆的纵横坐标也不相同.
A>简单的解决的办法是用SCREEN 12.

PAINT是填充图形,PAINT(X,Y),填充色,边框色
如果省略边框色默认是和填充色一样的。QB的填充方法跟PHOTOSHOP可不一样,

它是寻找边框色而不是寻找不同颜色.如果没遇到边框色,会把整个屏幕都充
满,有时遇到一些图形却不能充满,比如两个圆组成的环,你可以试试看。
PAINT还可以填充花纹图形。我觉得实际用途也不大,而且要了解显示屏幕页
面的知识,在M$的手册里讲了4大篇,我就懒得说了。
在QB的范例里有个EDPAT.BAS文件,是示范如何使用花纹填充的。
无论是QB还是MSBASIC,都附带许多示范,虽然做的挺漂亮,但不容易看懂。
你可以运行试试看。
下面是我高中时在GWBASIC上编的一个程序,去了行号,是关于花纹填充的。
--- ================================
DATA 255,75,75,123,255,149,149,221,255,106,106
DATA 239,255,253,253,255,255,38,38,231,255,25
DATA 25,127,255,100,100,124,-1
SCREEN 9
PALETTE 0, 1
w:
READ a
IF a = -1 THEN GOTO E:
a$ = CHR$(a) + a$
i = i + 1
GOTO w:
E:
PAINT (12, 12), a$
--- ================================
由于GWBASIC不是结构化语言,上面这个程序编的不好,用了GOTO语句,
其实完全可以用WHILE代替,读者自己改成WHILE吧。

READ命令是读取DATA语句存放的数据。如果有大量数据一般放到单独的
文件里,但速度较慢。如果少量数据,不如就用DATA命令。
DATA命令后各数据间用逗号分隔,写多少行都可以,QB认为他们都是连
在一起的。DATA可以放到程序中任何位置,QB自动跳过。
DATA命令的存放方式是字符串,即使编译成EXE文件后也能用PCTOOLS等
“看见”文本状态的数据。因此不能用引号,因为引号代表字符串,但
没有引号也代表字符串。由READ命令判断输入数据是数字还是字符串。
如果光输逗号则被认为是空字符串。
READ语句是顺序读取数据的,读到最后如果没有数据则会出错。
READ语句可以同时读取不同类型的几个变量。
可以用RESTORE语句恢复起始位置,从头开始。RESTORE命令
后也可加行号,表示恢复到某一行。比如:

data1:
DATA 2,2
data2:
DATA &HFF,3,a,4,5,b
... ...
RESTORE data2
READ a,b,c$
? a,b,c$

输出结果:
255 3 a
其中前两个是数字,最后一个是字符串。

上面我们解决了二维变换的问题,如果程序需要放大缩小旋转等操作
一般还是用数学方法来解决,这里就不多讨论了。

下面我们讨论三维图形:
--- ===================
SCREEN 12
xu = 350
yu = 80
zu = -30
z = 10 'Z的初值
WIDTH ,30 '你可以把这里换成60试试。模式12的文本模式有80X30和80X60两种

'注意逗号不可少,因为第一个参数是列,第二个才是行.
COLOR 13
LOCATE 27,1
PRINT "3D test"
COLOR 9
DO
p = -zu / (z - zu)
FOR x = 0 TO 1000 STEP 2 '步长值的大小决定画出来的速度和质量,改改看

y = 10 * SIN(.05 * SQR(x * x + z * z))
x1 = xu + (xu - x) * p
y1 = yu + (xu - y) * p
PSET (x1, y1)
NEXT
z = z + 5
LOOP WHILE INKEY$ = ""
--- ===================
三围....咳....三维变换公式是:
p = -zu / (z - zu)
x1 = xu + (xu - x) * p
y1 = yu + (xu - y) * p
其中x,y,z是要显示象素的三维坐标,xu,yu,zu是视角(就是你的眼睛)的坐标,

因为把Z轴方向指向屏幕里,所以你的眼睛的Z坐标是负的。X,Y坐标的指向跟
以前一样。而x1,y1就是投影变换以后的屏幕坐标了。
原理我不说了,通过这个公式可以把空间上一点投影到屏幕上,让眼睛感觉上是

立体的。
y = 10 * SIN(.05 * SQR(x * x + z * z))是一个三维曲线函数,就是你所
看到的那个波浪图。

PSET是画点,后面可以加颜色,比如 PSET(10,10),10
但我省略了,QB按前面COLOR命令设的默认颜色画。实际上所有绘图
命令都可以省略颜色。
在图形模式下打印字跟文本模式一样只是文本分辨率不同。模式9
还是80X25;模式12有两种,一个是80x30一个是80x60,可以用WIDTH
设置;模式13只有40X25,而且返回模式0后仍然保持40X25,比如要用
WIDTH 80改回80X25。打印的颜色取决于COLOR命令
与PSET对应的还有一个PRESET语句,是用来擦点,我觉得没什么用。

我们以前已经见到过COLOR命令了,是在文本模式下。
在模式9里背景颜色是整个屏幕的背景,只要一改整个全改,而不是个别字符。

在模式12和13里根本不能用背景颜色这项,COLOR命令只能有一个参数
就是前景色。
因此如果在上面写字的话文字的背景色一定是黑色的,除非用PALETTE改配色器。


绝对坐标和相对坐标:以前所用的都是绝对坐标,是相对(0,0)的坐标,
QB也可用相对坐标,在每个坐标前加STEP就成了,这时括号里的坐标
是在上一次绘图的坐标基础上移动的数值。

还有个绘图语句是DRAW,它是一种小型的语言,用字符串表示,
有点象LOGO语言。
看下面这段程序:
SCREEN 12
DRAW "bm200,100" '把绘图点移动到200,100,前缀b代表不画
DRAW "c1;s50;" 'c后跟的是颜色,S后是比例,可以改改比例看看。
FOR r = -360 TO 360 STEP 144
DRAW "TA" + STR$(r)
DRAW "r10"
SLEEP
NEXT

SLEEP是按任意键继续的命令,后面可以加个数字,表示等待多少秒后
继续,如果不加就只能永远期待你按键盘了。
关于DRAW的绘图命令请自己看HELP吧,我认为没什么用,很少用过。
比如下面这段程序是我刚翻出来的,是高中时编着玩的:
CONST pi = 3.1415926535#
SCREEN 12
VIEW (1, 1)-(280, 191), , 1
VIEW
PAINT (319, 199), 1, 1
a = 20 - 180
l = 90
x = 90
y = 55
PSET (x, y)
FOR i = 1 TO 9
SLEEP
a = a - 20 + 180
y = y - l * SIN(a * pi / 180)
x = x + l * COS(a * pi / 180)
LINE -(x, y)
NEXT
LOCATE 20: PRINT "Copyright (c) W.H.C": PRINT "corporation"
SLEEP

通过这个九角星的程序能进一步理解VIEW和LINE语句的用法。
LINE语句是可以省略第一个坐标参数的,这时就以上次画的最后
一点为起点开始画。


绘图语句刚说完就费了这么大篇幅,动画和屏幕页都没讲呢。 :(
今天又太累了,不写了。以后我可能还要再写关于鼠标,
EMS内存切换,声卡编程等等比较高级的QB程序编程方法,也许
写VB初步入门,不知道写哪方面的好。

留个"作业"吧,前面有钟表的例子,请用绘图命令编个漂亮点的钟表。





这部是最精彩的,你将可以编3D动画了!

补充点,上次忘了说那个3D图形的公式中SQR函数是开平方,与x^.5的
作用是相同的,但速度比后者快.
而X*X这种用法也是为了提高速度而不用X^2

上次我们讨论了QB的基本绘图命令,然而用那些命令要做动画是不成的.
我们知道,动画的原理就是一幅幅反复显示出来,在每一时刻都只有一帧
图象. 最简单的动画要属擦图动画了,看下面这段例子:
--- =====================================
SCREEN 12
CONST r = 10
COLOR 14
LINE (0, 200 + r)-(640, 200 + r), 10
FOR i = 0 TO 320 STEP .5
x = i * 2
y = 200 - ABS(SIN(i * .2) * 200) 'ABS取绝对值
CIRCLE (x, y), r
PAINT (x, y)
t1! = TIMER '延时开始
WHILE TIMER < t1! + .01
WEND '延时结束
LINE (x - r, y - r)-(x + r, y + r), 0, BF
IF INKEY$ = CHR$(27) THEN END
NEXT
--- =====================================
不好意思,我用取绝对值的正弦函数代替自由落体的函数.
似乎是动起来了,效果也不错.但因为这是一个简单的图形,画的时间
比较短,现在的CPU时间比较快. 如果你把延时部分放到LINE的后面
或者去掉你就知道问题所在了.
如果是复杂的图形,当画的过程所费时间与延时时间相近时就会发生闪烁.
事实上这个程序也有闪烁,只是速度比较快,眼睛反应不过来.
你可以试试把半径R改大一些.
QB提供了GET和PUT语句来提高绘图的速度.
下面修改一下上面的程序:
--- =====================================
SCREEN 12
CONST r = 20
COLOR 14
bpp = 1
Planes = 4
l = 4 + INT(((r * 2 + 1) * (bpp) + 7) / 8) * Planes * (r * 2 + 1)
l = l / 2 + 1
DIM cir(l) AS INTEGER
CIRCLE (200, 200), r
PAINT (200, 200)
GET (200 - r, 200 - r)-(200 + r, 200 + r), cir(0)
CLS '清屏语句
LINE (0, 300 + r)-(640, 300 + r), 10
FOR i = 20 TO 300 STEP .5
x = i * 2
y = 300 - ABS(SIN(i * .2) * 200)
PUT (x - 20, y - 20), cir(0)
t1! = TIMER
WHILE TIMER < t1! + .01
WEND
LINE (x - r, y - r)-(x + r, y + r), 0, BF
IF INKEY$ = CHR$(27) THEN END
NEXT
--- =====================================
我们把复杂的图形事先画好,用GET语句把点阵数据保存在一个数组变量里,
然后在动画循环中用PUT命令一次画出,效果好一些.
但数组变量的大小应该是多少呢?多了浪费,少了要出错误,QB的HELP里有这样
一个公式:

l = 4 + INT(((X2-X1+ 1) * (bpp) + 7) / 8) * Planes * (Y2-Y1 + 1)
X1,X2,Y1,Y2是图形块的左上和右下角坐标,bpp和planes与屏幕模式有关:
Screen Mode bpP Planes
9 1 4 (if > 64K of EGA memory)
12 1 4
13 8 1
计算出的L是字节长.而在QB里的数字变量没有单字节的,最短也是2字节
的INTEGER.因此我们用L/2+1来表示INT数组长度.
其实也可以用其他类型的数组的.

需要注意的是,与其他语句不同,所有坐标必须都在屏幕的物理范围之内,
否则将会出错.

PUT语句默认是把GET语句记录的图象与要覆盖的背景进行XOR运算.
我们再复习一下各种逻辑运算:
NOT 取非,就是NOT 1=0,NOT 0=1
这里的1是二进制位,不是数字,数字应是-1,因为二进制1111...等于-1
AND 与运算,如果x AND y,只有两个数字都是1才等于1,否则为0
OR 或运算,只要有一个是1,结果就是1
XOR 异或,当两边不相等为1,否则为0.
XOR有一种非常有趣的性质,就是任意交换性,比如A XOR B=C,
则A XOR C=B,C XOR B=A....任意两个数交换位置都可以.
而且只要不是0,通常运算结果跟运算前不同.
这种性质给加密等操作带来很大的方便.

PUT语句后面可以加上各种符号,表示原图与背景进行的运算方式.
前面说QB模式是XOR方式,还有PSET方式(直接覆盖),PRESET方式
(取非后覆盖),OR方式(与背景取或),AND方式(与背景取与)

看看下面这段程序,我们把前面的程序加上背景,事实上大多数动画都是要有
背景的.
--- =====================================
SCREEN 12
CONST r = 20
COLOR 14
bpp = 1
Planes = 4
l = 4 + INT(((r * 2 + 1) * (bpp) + 7) / 8) * Planes * (r * 2 + 1)
l = l / 2 + 1
DIM cir(l) AS INTEGER
CIRCLE (200, 200), r
PAINT (200, 200)
GET (200 - r, 200 - r)-(200 + r, 200 + r), cir
CLS
FOR i = 0 TO 640 STEP 10 '画背景
LINE (i, 0)-(i, 479), 3
NEXT

LINE (0, 300 + r)-(640, 300 + r), 10

FOR i = 20 TO 300 STEP .5
x = i * 2
y = 300 - ABS(SIN(i * .2) * 200)
PUT (x - 20, y - 20), cir, XOR '试把这里改成其他符号,如PSET等.
t1! = TIMER
WHILE TIMER < t1! + .01
WEND
PUT (x - 20, y - 20), cir '默认是XOR,可以在保留背景的同时擦图
'LINE (x - r, y - r)-(x + r, y + r), 0, BF '原来的方法把背景也给擦

IF INKEY$ = CHR$(27) THEN END
NEXT
--- =====================================
我们发现,XOR的缺点是只要不是黑色就会变色,跟以前不一样了.
因此要做得好一点应该先把要被覆盖的背景用个数组存下来,
当下一帧时再把那个"备份"覆盖回去,缺点是闪烁感很强.

其实擦图动画永远解决不了闪烁的问题的,因为屏幕上总有一段时间
那个图形消失了,一亮一灭,这就造成了闪烁,无论速度多快,只要
屏幕上显示了不该显示的东西,人眼就能反映出来.

真正的解决办法是用双缓冲技术.这种技术把要画在屏幕上的东西事先
在内存中画出来,然后把整个页全部覆盖. 然而QB对内存的控制很严,
给你操作内存的语句很少也很慢,根本不能完成这种技术,而且所有
绘图语句也都不能用,要用位运算来完成.
其实象JAVA语言一样,这些限制的好处是安全性提高了,但JAVA本身就提供
了双缓冲. :(
替代的办法是屏幕页技术. 显示卡从硬件上来说,显示内存足够存放
很多页.可由于编QB时内存还很贵,通常的显示卡只支持模式9的双
屏幕页,这也是为什么我要讲模式9的原因.
模式9从颜色属性来说跟模式12一样,但发色数和分辨率要低得多.
由于分辨率是640X350,象素是长的点而不是方点,如果你画个正方形
就会发现变长方形了,这需要注意. 但CIRCLE会自动调整纵横比,画出来
总是圆的.

屏幕页的控制用SCREEN语句的第三,四个参数:
SCREEN ,,绘图页,显示页
注意前面那两个逗号少不得.默认两个页都是0.
画图时在绘图页上画,在屏幕上显示不出来,屏幕上显示的是显示页,
两页无关,这样用户就不会看见绘图过程.当绘图页画好后你当然可以
用SCREEN语句把绘图页和显示页交换,但实际上交换时可能会发生闪烁,
这是硬件的原因.因此我们用PCOPY语句把一页拷贝到另一页去,速度很快.
再看下面这个例子:
--- =====================================
SCREEN 9
CONST r = 40 '加大半径,如果你把以前的例子也加大半径就会发生严重的闪烁

COLOR 14
bpp = 1
Planes = 4
l = 4 + INT(((r * 2 + 1) * (bpp) + 7) / 8) * Planes * (r * 2 + 1)
l = l / 2 + 1
DIM cir(l) AS INTEGER, backup(l) AS INTEGER
CIRCLE (200, 200), r
PAINT (200, 200)
GET (200 - r, 200 - r)-(200 + r, 200 + r), cir
CLS
FOR i = 0 TO 640 STEP 10
LINE (i, 0)-(i, 479), 3
NEXT

LINE (0, 300 + r)-(640, 300 + r), 10
PCOPY 0, 1
SCREEN , , 0, 1 ' 显示第0页绘图,第1页显示
FOR i = 20 TO 300 STEP .5
x = i * 2
y = 300 - ABS(SIN(i * .2) * 200)
GET (x - r, y - r)-(x + r, y + r), backup '先把要覆盖的背景备份
PUT (x - r, y - r), cir '你可以用任何方法贴图.
PCOPY 0, 1
t1! = TIMER
WHILE TIMER < t1! + .01
WEND
PUT (x - r, y - r), backup, PSET '恢复背景
'LINE (x - r, y - r)-(x + r, y + r), 0, BF
IF INKEY$ = CHR$(27) THEN END
NEXT
--- =====================================
不错吧?一点闪烁都没有.
但QB做动画还是不成,因为没有透明色方式. 在动画中动的物体通常被称
为"精灵",精灵的背景通常是黑色,但如果背景不是黑色,用PSET就会很难看,
如果用XOR精灵的"身体"就会变色.这时就需要一种"通明色",贴上去只显示
身体,不显示背景. 很不幸的,DOS下的程序都很难完成这种操作,WIN下恐怕
也要DIRECTX技术的帮助,我原来曾想用汇编语言编函数,可后来一想,都WIN95
时代了,DOS就凑合用吧.

动画技术基本讲完了,一般的书上还有"字符动画",我觉得意义不大,
字符模式可以有4到8页,但不能用PCOPY,闪烁好象更厉害. :P
可以参考我的BATTOOL3.2版,我想很多站的DOS工具区都有这个软件.
里面有用字符动画做的一只小猪的动画,还是用批处理做的,改成QB
程序太容易了.

但是绘图技术不仅这些,它需要很多的数学知识. 比如我那本最早的BASIC
书里讲述了各种用矩阵变换计算各种二维三维图形,各种三维图形的做法等.
<电脑爱好者>几年前也连载过用QB做动画的教程,似乎是能做出来三国志的
片头动画(但效果可是....嘿嘿,能看出来是人就不错了)
希望在实际应用时多运用数学工具,先想出数学模型,在转换成QB语句.


下面我们要讲模块化编程了.
主要有两个语句:SUB 和 FUNCTION
SUB的作用是自己定义一个过程,以后在QB里可以象调用语句那样调用它,
或者说就是"自定义语句"或是"子程序".
FUNCTION的作用就是"自定义函数",可以象调用函数那样调用FUNCTION过程.
使用过程除了简化一段反复使用的程序外,它可以使一个复杂的工程问题
简单化,先解决"主要矛盾",把一些难解决的细节问题放到过程里.
在过程的内部的变量和其他过程,主程序间是没有关系的.
也就是说你可以在过程外边用for i=....循环,循环里面有个过程,
过程里面也有个for i=...循环,但这两个变量i是没有关系的.
这就是过程的优点,当程序很大的时候,变量很多,闹不好就用重复的名字,
很容易出错,可过程间变量名相同也没关系.
当然你也可以让某些变量成为"全局变量",比如在主程序里输入:
DIM SHARED I AS INTEGER
那么无论是那个SUB或者FUNCTION,只要有I,都是指的同一个I.
QB会对过程进行"隔离"处理,使每个模块看起来都很小,心理上
似乎觉得容易了. 切换各模块是用*F2键*.

首先我们来看FUNCTION,其实就是"自定义函数",但这个名称在老式BASIC
里已经用了,我也不知道FUNCTION中文是什么. :P
我们知道函数的特点是有几个参数,一个返回值.
比如SIN(X),X是参数,返回一个数是计算结果.
我们也来定义一个函数:
--- ============================
DECLARE FUNCTION a% (x AS INTEGER) '这行是QB自动加的
WHILE (INKEY$ = "")
PRINT myfunc(y%)
WEND

FUNCTION myfunc% (x AS INTEGER) '函数开始
STATIC c AS INTEGER
c = c + 1
myfunc = c
END FUNCTION
--- ============================
当你输入FUNCTION时QB就会自动开个新的屏幕,好象是个新的程序,
这就是模块化,模块间的关系只通过参数传递来联系.
函数的返回值放到一个特殊的变量中,变量名跟函数名一样,
myfunc=c的意思就是整个函数的结果等于c.
需要注意的是这个变量只能赋值,不能用myfunc=myfunc+1,否则
可能会出现很严重的后果,因为这种调用叫做递归调用,见后面的解释.
STATIC表示静态变量,在第二行开头加上个"'"注释掉,看看会怎样?
静态变量的意思就是这个变量在内存的位置始终不变,每次调用都保持上次
的值.也可以在FUNCTION a(..)的后面加上STATIC表示这是个静态函数,
所有变量都是静态的.你可以加上/去掉试试看有什么区别.
默认情况下每一次调用都不同,值也不同. 这样才能进行递归调用.
所谓递归就是自己调用自己的一种算法,在计算机编程里是一种很重要的
算法,很多数学问题(比如著名的河内之塔问题)必须要用递归.
为了安全,QB递归有次数限制的,超过要出错误提示,不会象其他语言那样崩溃.
我们用QB编个计算阶乘的函数: 阶乘的定义是 a!=a*(a-1)*...3*2*1
(叹号是阶乘符号) 由此可以知道 a! = a*(a-1)!, (a-1)!=(a-1)*(a-2)!...
看下面这个程序:
--- ============================
DECLARE FUNCTION jc% (x AS INTEGER)
PRINT jc(3)

FUNCTION jc% (x AS INTEGER)
IF x <= 1 THEN
jc = 1
EXIT FUNCTION
END IF
jc = x * jc(x - 1)
END FUNCTION
--- ============================
a就是一个阶乘函数,主程序只有一句 print语句,因此你完全可以在
立即窗里输入 ?a(9) 立刻得到9的阶乘是多少.
为了理解递归的过程,你可以在jc=x*...那行的前后各加一个PRINT X
看看递归调用的顺序.注意千万不能 PRINT JC,因为那就成递归调用了.

有时我们要反复的使用一段程序,每次使用都相似,但某些变量稍有不同,
我们就可以用SUB简化程序.
当你输入SUB xxx(参数表)时QB会自动换一个新的屏幕,并替你添好END SUB,
这是SUB结束命令.
DECLARE命令是过程声明,表示你使用了哪个过程很不好拼写?没关系,你也不用写
.
但当混合语言编程时必须写DECLARE,不过我们暂时用不着.
调用过程时直接写
xxx 参数表 ,不要括号,就跟其他QB命令一样.也可以用
CALL xxx(参数表) 调用
下面的例子说明了SUB的作用,因为反复调用了两次

看下面这个例子:
DECLARE SUB hos (xv!, yv!, zv!, z1!, z2!) '这行是系统自动输入的,不用输

OPTION BASE 1 '设置所有的数组的最低下标为1,而不是0
SCREEN 9, , 0, 1 '你可以试着把9后面的东西去掉,可以看出使用双屏幕页的
优点
WINDOW SCREEN (-100, -100)-(200, 200)
FOR z = 400 TO 0 STEP -25
COLOR 15 - (z MOD 14)
hos -100, 80, -50, z, z + 20
NEXT
WINDOW
x = -300
Xstep = 15 '此处可调动画速度
LINE (0, 0)-(640, 170), 13,BF
COLOR 14
DO
IF INKEY$ <> "" THEN END
LINE (0, 70)-(370, 150), 0, BF
hos x, 80, -50, 2, 24
PCOPY 0, 1
x = x + Xstep
IF x < -300 OR x > 800 THEN Xstep = -Xstep '碰撞效果
LOOP
DATA 50,100,50,150,150,150,150,100,50,100,100,75,150,100 '五边型坐标


SUB hos (xv, yv, zv, z1, z2)
DIM x1(7) AS INTEGER, x2(7) AS INTEGER, y1(7) AS INTEGER, y2(7) AS INT
EGER
RESTORE
p1 = -zv / (z1 - zv) '第一个五边形
p2 = -zv / (z2 - zv) '第二个五边形
FOR i = 1 TO 7
READ x, y
x1(i) = xv + (x - xv) * p1
y1(i) = yv + (y - yv) * p1
x2(i) = xv + (x - xv) * p2
y2(i) = yv + (y - yv) * p2
NEXT
FOR i = 1 TO 6
LINE (x1(i), y1(i))-(x1(i + 1), y1(i + 1))
LINE (x2(i), y2(i))-(x2(i + 1), y2(i + 1))
LINE (x1(i), y1(i))-(x2(i), y2(i))
NEXT
LINE (x1(7), y1(7))-(x2(7), y2(7))
END SUB

如何?是不是很兴奋?自己也能做出这样的三维动画! 你可以结合PALETTE
等命令不断改变房子的颜色,让它更加奇幻一些.

OPTION BASE 语句后只能跟0或者1,当为1时数组定义a(n)相当于a(1 to n)
房子的画法很简单,七个点是房子截面的五个角再加一条横线,画两个五
边形再把对应顶点连起来就是个房子了.把这14个点分别用透视变换转成
三维投影再用LINE连线就出房子了. 因为三维的直线的投影仍然是直线.
第一个FOR循环画一串的房子,为了使前面的房子盖住后面的房子,是
倒着画的. 房子很不好看,因为把应该被挡住的线也画出来了.
三维遮掩技术很复杂,如果简单的点构成的图形可以用个数组存放每个点
的Z轴投影,只把最前面(Z最小)的点显示出来,但对于复杂图形或者以面
构成的图形,需要判断多边形(POLYGON)是前是后,这需要高等数学
的向量矩阵变换,还有填充技术也很复杂,鉴于读者如果学过这些知
识多半就不会看本文了,本文也就不细讨论.而且现在的三维动画
通常都直接使用DIRECT3D技术,用不着考虑这么多数学问题.

后面的动画是不断移动视点位置,造成移动效果.由于刚讲完动画,就不多说了.

由于两次画房子的操作,而虽然画的房子不一样,但画房子的方法是一样的,
于是我们就定义一个HOS过程,只要传递给过程房子两个五边形的Z坐标,
眼睛的位置就能显示出来同样的房子模型,甚至可以用WINDOW改变房子
的比例.

这个程序的主要思想是画动画和一串房子,画房子虽然很麻烦,但我们
可以把它扔到SUB里去以后再想,专心琢磨如何编动画. 由于QB对过程
的"封装",他们之间都是"隔离"的,每个模块都不大,用不着翻很多页
去找另一段程序了,只要按一下F2就成了. 而且由于你的过程与具体数据
间联系不大,这个模块以后的程序还可以再用,不用重新编.
这种编程方法就叫模块化设计, 我们老师教的是要"由上至下",就是
先编主程序,再编模块. 可我的习惯总是相反,因为有的模块可能不容易
编出来,要用另外的方法去做,因此还要翻回去改主程序,这样就会影响
到整个程序. 无论怎样编,采用模块化的思想将会使你编大程序的时候
不会觉得枯燥,更容易调试,不容易出错.
比如上面那个房子,我在主程序还没编好时就可以看看画出来的房子
是否正确,如果所有程序都混在一起,很难挑出来错在哪里.
而且编到一半就能看到效果,也解了一部分编程的枯燥,因为编程
虽然枯燥无味,但成功的喜悦是非常美妙的. :)

... 还没完哦!
还有事件与声音、游戏杆、键盘、MODEM控制,和错误与文件处理,
系统功能调用和混合语言编程,高级QB编程专题(Mouse,声卡,EMS内存,
目录处理.......我想把我的QB经验都写出来,然而.......
可我还连提纲都没想好哪!
主要是没有动力,写了没人看。:(
有人说多贴点例子,我原来贴过不少的,似乎是没人看。

标签集:TAGS:
回复Comments() 点击Count()
喜欢就顶一下

回复Comments

{commentauthor}
{commentauthor}
{commenttime}
{commentnum}
{commentcontent}
作者:
{commentrecontent}