·251·
C语言高级编程技术
C语言高级编程技术
8.1 递归程序设计
8.1.1 递归与递归程序设计
递归技术在算法和程序设计中是一种十分有用的技术,C语言提供了支持递归定义的机制和手段。递归有直接递归和间接递归两种。在一个函数的定义中出现了对自身的调用,称之为直接递归;一个函数f的定义中包含了对函数g的调用,而g的实现过程又调用了f,即函数调用形成了一个环状调用链, 这种方式称之为间接递归。
例8.1 编写一个递归函数,求n的阶乘值n!。
若用fact(n)表示n的阶乘值,根据阶乘的数学定义可知:
显然, 当n>0时,fact(n)是建立在fact(n-1)的基础上。由于求解fact(n-1)的过程与求解fact(n)的过程完全相同,只是具体实参不同,因而在进行程序设计时,不必再仔细考虑fact(n-1)的具体实现,只需借助递归机制进行自身调用即可。于是求n的阶乘值fact(n)的具体实现为:
long fact(int n)
{
long m;
if (n == 0)
return(1);
else
{
m=n*fact(n-1);
return(m);
}
}
例8.2 编写一个递归函数,求Fibonacci数列第n项的值。
若用Fibona(n)表示Fibonacci数列第n项的值,根据Fibonacci数列的计算
公式
小学单位换算公式大全免费下载公式下载行测公式大全下载excel公式下载逻辑回归公式下载
:
可知当n>2 时,Fibonacci数列第n项的值等于第n-1项的值与第n-2项的值相加之和,而Fibonacci数列第n-1项和第n-2项值的求解又分别取决于它们各自前两项之和。总之,Fibona(n-1)和Fibona(n-2)的求解过程与Fibona(n)的求解过程相同,只是具体实参不同。利用以上这种性质,我们在进行程序设计时便可以使用递归技术,Fibona(n-1)和Fibona(n-2)的求解只需调用函数Fibona自身加以实现即可。具体实现为:
int Fibona(int n)
{
int m;
if (n==1 || n==2)
return (1);
else
{
m=Fibona(n-1)+ Fibona(n-2);
return (m);
}
}
从上面两个实例可以看出,要使用递归技术进行程序设计,首先必须将要求解的问
题
快递公司问题件快递公司问题件货款处理关于圆的周长面积重点题型关于解方程组的题及答案关于南海问题
分解成若干子问题,这些子问题的结构与原问题的结构相同,但规模较原问题小。由于子问题与原问题结构相同,因而它们的求解过程相同,在进行程序设计时,不必再仔细考虑子问题的求解,只需借助递归机制进行函数自身调用加以实现,然后利用所得到的子问题的解组合成原问题的解即可;而递归程序在执行过程中,通过不断修改参数进行自身调用,将子问题分解成更小的子问题进行求解,直到最终分解成的子问题可以直接求解为止。
综上所述,递归程序设计具有以下两个特点:
(1)具备递归出口。递归出口定义了递归的终止条件,当程序的执行使它得到满足时,递归执行过程便终止。有些问题的递归程序可能存在几个递归出口;
(2)在不满足递归出口的情况下,根据所求解问题的性质,将原问题分解成若干子问题,子问题的求解通过以一定的方式修改参数进行函数自身调用加以实现,然后将子问题的解组合成原问题的解。递归调用时,参数的修改最终必须保证递归出口得以满足。
8.1.2 递归程序执行过程的分析
递归程序的执行过程分为递推和回归两个阶段。
在递推阶段,把较复杂的问题(规模为n)的求解推到比原问题简单一些的问题(规模小于n)的求解。例如例8.2中,求解Fibona(n),把它推到求解Fibona(n-1)和Fibona(n-2)。即是说,为计算Fibona(n),必须先计算Fibona(n-1)和Fibona(n-2),而计算Fibona(n-1)和Fibona(n-2),又必须先计算Fibona(n-3)和Fibona(n-4)。依次类推,直至计算Fibona(1)和Fibona(2),分别能立即得到结果1和1。在递推阶段,必须要有终止递归的情况。例如在函数Fibona中,当n为1和2的情况。
在回归阶段,当获得最简单情况的解后,逐级返回,依次得到稍复杂问题的解,例如得到Fibona(1)和Fibona(2)后,返回得到Fibona(3)的结果,……,在得到了Fibona(n-1)和Fibona(n-2)的结果后,返回得到Fibona(n)的结果。
在编写递归函数时要注意,函数中的局部变量和参数知识局限于当前调用层,当递推进入“简单问题”层时,原来层次上的参数和局部变量便被隐蔽起来。在一系列“简单问题”层,它们各有自己的参数和局部变量。
由于递归引起一系列的函数调用,并且可能会有一系列的重复计算,递归算法的执行效率相对较低。当某个递归算法能较方便地转换成递推算法时,通常按递推算法编写程序。例如上例计算斐波那契数列的第n项的函数Fibona(n)应采用递推算法,即从斐波那契数列的前两项出发,逐次由前两项计算出下一项,直至计算出要求的第n项。
由于递归调用是对函数自身的调用,在一次函数调用未结束之前又开始了另一次函数调用。这时为函数的运行所分配的空间在结束之前是不能回收的,必须保留。这也意味着函数自身的每次不同调用,就需要分配不同的空间。只有当最后一次调用结束后,才释放最后一次调用所分配的空间,然后返回上一层调用,调用结束后,释放调用所分配的空间,再返回它的上一层调用,这样逐层返回,直至返回到第一次调用,当第一次调用结束后,释放调用所分配的空间,整个递归调用才完成。
在例8.1中,给出了一个求阶乘的函数。下面以求4!为例,其调用过程如图8-1所示。
要求4!,即要求的fact(4)值。
图8-1 递归函数调用的执行过程
8.1.3 递归算法的优缺点
递归函数的主要优点是可以把算法写的比使用非递归函数时更清晰更简洁,而且某些问题,特别是与人工智能有关的问题,更适宜用递归方法。
递归算法的缺点,一是需要额外的内存开销,特别是当递归层次较大时,递归函数需要占用的堆栈空间相当大。二是递归引起一系列的函数调用,并且可能会有一系列的重复计算,递归算法的执行效率相对较低。总之,递归算法要比解决同样问题的非递归算法效率低一些。内存空间需求更多一些。
大多数用递归算法解决的问题,都可以找到相应的非递归算法,只有少数问题的求解只有递归算法。由于递归算法具有效率低、内存消耗大等缺点,在设计程序时,若有比较好的非递归算法,应尽量采用非递归算法。
8.1.4 递归程序设计的应用实例
例8.3 编程实现将正整数转换为字符串。要求在主函数中输入正整数,转换以及输出编一递归函数完成。
本例的关键在于设计一个递归函数完成正整数n到字符串的转换,实现该函数的一个基本思想为:从高位到低位分别取出n的每一位上的数字,将它们转换成对应的字符后,按其原有的顺序输出;而在此转换过程中,将n前面的若干位(除个位外)对应的整数转换成字符串的过程与将整个整数转换成字符串的过程完全相同,只是处理的对象不同,因此可以通过递归调用实现,然后在此基础上再将n的个位数字转换成字符输出即可。显然,若n前面的若干位(除个位外)对应的整数为0时,递归调用应该终止。程序为:
#include
void convert(int n)
{
int i,c;
if ((i=n/10)!=0)
convert(i);
c= n%10+ '0';
putchar(c);
}
void main()
{
int a;
scanf("%d",&a);
convert(a);
}
例8.4 编程求两个正整数的最大公约数。要求编写一个递归函数求最大公约数。
求最大公约数gcd(m,n)的求解公式为:
由于以上最大公约数的定义本身即为递归定义,因此采用递归方式实现求m和n的最大公约数问题十分方便,将n=0作为递归的终止条件,其它情况只需按公式进行递归调用即可。程序为:
#include
int gcd(int m,int n)
{
int k;
if (n==0)
return(m);
else if (n>m)
return(gcd(n,m));
else
{
k=m%n;
return(gcd(n,k));
}
}
void main()
{
int a,b;
scanf("%d%d",&a,&b);
printf("%d",gcd(a,b));
}
例8.5 汉诺塔(Hanoi)问题。汉诺塔问题是一个著名的问题。约十九世纪末,在欧洲的商店中出售一种智力玩具,在一块铜板上有三根杆,最左边的杆上自上而下、由小到大顺序串着由64个圆盘构成的塔,游戏的目的是将最左边A杆上的圆盘,借助最右边的C杆,全部移到中间的B杆上,条件是一次仅能移动一个盘,且不允许大盘放在小盘的上面。如图8-2所示。
图 8-2 汉诺塔
由于问题中给出的圆盘移动条件是:一次仅能移动一个盘,且不允许大盘放在小盘的上面,这样64个盘子的移动次数是:18,446,744,073,709,551,616。这是一个天文数字,若每一微秒可能计算(并不输出)一次移动,那么也需要几乎一百万年。我们仅能找出问题的解决方法并解决较小n值时的汉诺塔,但目前由于计算机的速度还不够"快",尚不可能用计算机解决64层的汉诺塔。
按照上面给出的方法分析问题,找出移动圆盘的递归算法。
设要解决的汉诺塔共有n个圆盘,对A杆上的全部n个圆盘从小到大顺序编号,最小的圆盘为1号,次之为2号,依次类推,则最下面最大的圆盘的编号为n。
第1步,先将问题简化。假设A杆上只有一个圆盘,即汉诺塔只有一层n=1,则只要将1号盘从A杆上移到B杆上即可。
第2步,对于一个有n(n>1)个圆盘的汉诺塔,将n个圆盘分为两部分:上面的n-1个圆盘和最下面的n号圆盘。
第3步,将"上面的n-1个圆盘"看成一个整体,为了解决n个圆盘的汉诺塔,可以按如下方式进行操作:
① A杆上面的n-1个盘子,借助B杆,移到C杆上(如图8-3所示);
图8-3
② A杆上剩下的n号盘子移到B杆上(如图8-4所示);
图8-4
整理上述分析结果,把第1步中化简问题的条件作为递归结束条件,将第3步分析得到的算法作为递归算法,可以写出如下完整的递归算法描述。
定义一个函数movedisc(n,Aneedle,Bneedle,Cneedle)。该函数的功能是:将Aneedle杆上的n个圆盘,借助Cneedle杆,移动到Bneedle杆上。这样移动n个圆盘的递归算法描述如下:
movedisc(n,Aneedle,Bneedle,Cneedle)
{
if ( n==1 )
将n号圆盘从Aneedle上移到Bneedle;
else
{
① movedisc(n-1,Aneedle,Cneedle,Bneedle)
② 将n号圆盘从Aneedle上移到Bneedle;
③ movedisc(n-1,Cneedle,Bneedle,Aneedle)
}
}
按照上述算法可以编出如下程序。
#include
int i=0; /* 移动圆盘数量计数器 */
void movedisc(unsigned int n, char Aneedle, char Bneedle, char Cneedle)
{
if(n==1)
printf("%2d-(%2d): %c ==> %c\n",++i,n,Aneedle,Bneedle);
/* 将Aneedle上的一个圆盘移到Bneedle上 */
else
{
movedisc(n-1, Aneedle, Cneedle, Bneedle);
/* 将Aneedle上的n-1个圆盘借助Bneedle移到Cneedle上 */
printf("%2d-(%2d): %c ==> %c\n",++i,n,Aneedle,Bneedle);
/* 将Aneedle上的一个圆盘移到Bneedle上 */
movedisc(n-1,Cneedle,Bneedle,Aneedle);
/* 将Cneedle上的n-1个圆盘借助Aneedle移到Bneedle上 */
}
}
void main( )
{
unsigned n;
printf("Please enter the number of discs:");
scanf("%d",&n); /* 输入n值 */
movedisc(n,'a','b','c'); /* 将A上的n个圆盘借助C将移动到B上 */
printf("\t Total: %d\n", i);
}
8.2 文本的屏幕输出和键盘输入
8.2.1 文本的屏幕输出
显示器的屏幕显示模式有两种:文本方式和图形方式。文本方式就是只能显示字符的方式,在文本模式下屏幕上可以显示的最小单位是字符。在文本模式下,坐标原点在屏幕左上角,其坐标为(1,1),X轴为水平方向,Y轴为垂直方向。
Turbo C 的字符屏幕函数主要包括文本窗口大小的设定、窗口颜色的设置、窗口文本的清除和输入输出等函数。这些函数的有关信息均包含在conio.h 头文件中,因此在用户程序中使用这些函数时,必须用include预处理命令将conio.h 包含进程序。
1) 文本窗口的定义
Turbo C 默认定义的文本窗口为整个屏幕,共有80 列25 行的文本
单元
初级会计实务单元训练题天津单元检测卷六年级下册数学单元教学设计框架单元教学设计的基本步骤主题单元教学设计
。除了这种默认的80 列25 行的文本显示方式外,还可由用户通过textmode()函数来显式地设置Turbo C 支持的文本显示方式。Textmode()函数的函数原型为:
void textmode(int newmode);
该函数将清除屏幕,以整个屏幕为当前窗口,并移光标到屏幕左上角。newmode 参数的取值见表8-1,既可以用表中指出的方式代码,又可以用符号常量。LASTMODE 方式指上一次设置的文本显示方式,它常用于在图形方式到文本方式的切换。
方式
符号常量
显示列×行数和颜色
0
BW40
40×25黑白显示
1
C40
40×25彩色显示
2
BW80
80×25黑白显示
3
C80
80×25彩色显示
7
MONO
80×25单色显示
-1
LASTMODE
上一次的显示方式
表8-1 Turbo C 支持的6种显示方式
Turbo C 也允许用户根据自己的需要通过使用窗口设置函数window()重新设定显示窗口。window()函数的函数原型为:
void window(int left, int top, int right, int bottom);
函数中形参(int left,int top)是窗口左上角的坐标,(int right,int bottom)是窗口的右下角坐标,其中(left,top)和(right,bottom)是相对于整个屏幕而言的。例如,要定义一个窗口左上角在屏幕(20,5)处,大小为30 列15 行的窗口可写成:
window(20, 5, 50, 25);
若window()函数中的坐标超过了屏幕坐标的界限,则窗口的定义就失去了意义,也就是说定义将不起作用,但程序编译连接时并不出错。
窗口定义之后,用有关窗口的输入输出函数就可以只在此窗口内进行操作而不超出窗口的边界。
一个屏幕可以定义多个窗口,但现行窗口只能有一个(因为DOS 为单任务操作系统)。当需要用另一窗口时,可将定义该窗口的window()函数再调用一次, 此时该窗口便成为现行窗口了。
2) 文本窗口颜色和其它属性的设置
文本窗口颜色的设置包括背景颜色的设置和字符颜色(即前景色)的设置,使用的函数及其原型为:
设置背景颜色函数:void textbackground(int color);
设置字符颜色函数:void textcolor(int color);
有关颜色的定义见表8-2。表中的符号常数与相应的数值等价,二者可以互换。例如设定蓝色背景可以使用textbackground(1),也可以使用textbackground(BLUE),两者没有任何区别。
符号
常数
数值含义
背景或背景
BLACK
0
黑
前景、背景色
BLUE
1
蓝
前景、背景色
GREEN
2
绿
前景、背景色
CYAN
3
青
前景、背景色
RED
4
红
前景、背景色
MAGENTA
5
洋红
前景、背景色
BROWN
6
棕
前景、背景色
LIGHTGRAY
7
淡灰
前景、背景色
DARKGRAY
8
深灰
用于前景色
LIGHTBLUE
9
淡蓝
用于前景色
LIGHTGREEN
10
淡绿
用于前景色
LIGHTCYAN
11
淡青
用于前景色
LIGHTRED
12
淡红
用于前景色
LIGHTMAGENTA
13
淡洋红
用于前景色
YELLOW
14
黄
用于前景色
WHITE
15
白
用于前景色
BLINK
128
闪烁
用于前景色
表8-2 颜色表
Turbo C 另外还提供了一个函数,可以同时设置文本的字符和背景颜色,这个函数是文本属性设置函数,其函数原型为:
void textattr(int attr);
参数attr 的值表示颜色形式编码的信息,每一位代表的含义如下:
位
7
6
5
4
3
2
1
0
B
b
b
b
c
c
c
c
闪烁
背景颜色
字符颜色
字节低四位cccc 设置字符颜色,4~6 三位bbb 设置背景颜色,第7位B 设置字符是否闪烁。假如要设置一个蓝底黄字,定义方法如下:
textattr(YELLOW+(BLUE<<4));
若再要求字符闪烁,定义变为:
textattr(128+YELLOW+(BLUE<<4);
注意:
(1) 对于背景只有0到7共八种颜色,取大于7小于15 的数,则代表的颜色与减8后的值对应的颜色相同;
(2) 用textbackground()和textcolor()函数设置了窗口的背景与字符颜色后,在没有用clrscr()函数清除窗口之前,颜色不会改变,直到使用了函数clrscr(),整个窗口和随后输出到窗口中的文本字符才会变成新颜色。
(3) 用textattr()函数时背景颜色应左移4位,才能使3 位背景颜色移到正确位置。
3) 窗口内文本的输入输出函数
(1) 窗口内文本的输出函数
前面介绍过的printf(),putc(),puts(),putchar()等输出函数以整个屏幕为窗口的,它们不受由window 设置的窗口限制,也无法用函数控制它们输出的位置,但Turbo C 提供了三个文本输出函数,它们受窗口的控制,窗口内显示光标的位置,就是它开始输出的位置。
当输出行右边超过窗口右边界时,自动移到窗口内的下一行开始输出,当输出到窗口底部边界时,窗口内的内容将自动产生上卷,直到完全输出完为止,这三个函数均受当前光标的控制,每输出一个字符光标后移一个字符位置。这三个输出函数原型为:
int cprintf(char *format,表达式表);
int cputs(char *str);
int putch(int ch);
它们的使用
格式
pdf格式笔记格式下载页码格式下载公文格式下载简报格式下载
同printf(),puts()和putc(),其中cprintf()是将按格式化串定义的字符串或数据输出到定义的窗口中,其输出格式串同printf 函数,不过它的输出受当前光标控制,且输出特点如上所述,cputs 同puts,是在定义的窗口中输出一个字符串,而putch()则是输出一个字符到窗口,它实际上是函数putc 的一个宏定义,即将输出定向到屏幕。
(2) 窗口内文本的输入函数
可直接使用stdio.h 中的getch()或getche()函数。需要说明的是,getche()函数从键盘上获得一个字,在屏幕上显示的时候,如果字符超过了窗口右边界,则会被自动转移到下一行的开始位置。
4) 有关屏幕操作的函数
void clrscr(void);该函数将清除窗口中的文本,并将光标移到当前窗口的左上角处。
void clreol(void);该函数将清除当前窗口中从光标位置开始到本行结尾的所有字符,但不改变光标原来的位置。
void delline(void);该函数将删除光标所在行的所有字符。
void gotoxy(int x, int y);该函数用于定位光标在当前窗口中的位置。这里x,y 是指光标要定位处的坐标(相对于窗口而言)。当x, y 超出了窗口的大小时,该函数不起作用。
int movetext(int x1, int y1, int x2, int y2, int x3, int y3);该函数将把屏幕上左上角为(xl,y1),右下角为(x2,y2)的矩形内文本拷贝到左上角为(x3,y3)的一个新矩形区内。这里x,y 坐标是以整个屏幕为窗口坐标系,即屏幕左上角为(1,1)。该函数与开设的窗口无关,且原矩形区文本不变。
int gettext(int xl, int yl, int x2, int y2, void *buffer);该函数将把左上角为(xl,y1),右下角为(x2,y2)的屏幕矩形区内的文本存到由指针buffer指向的一个内存缓冲区内,当操作成功,返回1;否则,返回0。
因一个在屏幕上显示的字符需占显示存储器VRAM 的两个字节,即第一个字节是该字符的ASCII 码,第二个字节为属性字节,即表示其显示的前景、背景色及是否闪烁,所以buffer 指向的内存缓冲区的字节总数的计算为:
字节总数=矩形内行数×每行列数×2
其中:矩形内行数=y2-y1+l,每行列数=x2-xl+1(每行列数是指矩形内每行的列数)。矩形内文本字符在缓冲区内存放的次序是从左到右,从上到下,每个字符占连续两个字节并依次存放。
int puttext(int x1, int y1, int x2, int y2, void *buffer);该函数则是将gettext()函数存入内存buffer 中的文字内容拷贝到屏幕上指定的位置。
注意:
(1) gettext()函数和puttext()函数中的坐标是对整个屏幕而言的,即是屏幕的绝对坐标,而不是相对窗口的坐标;
(2) movetext()函数是拷贝而不是移动窗口区域内容,即使用该函数后,原位置区域的文本内容仍然存在。
例8.6:下面的程序首先定义了一个字符数组,下标为64,表示用来存四行八列的文本。
由于没有用window 函数设置窗口,因而用缺省值,即全屏幕为一个窗口,程序开始设置80列×25 行文本显示方式(C80),背景色为蓝色,前景色为红色,经clrscr 函数清屏后,设置的背景色才使屏幕背景变蓝。gotoxy(10,10)使光标移到第10 行10 列,然后在(10,10)开始位置显示L:load,接着在下面三行相同的列位置显示另外三条信息,13 行10 列显示的E:exit 后面带有回车换行符,为的是将光标移到下一行开始处,好显示press any key to continue。当按任一键后,gettext 函数将(10,l0,18,13)矩形区的内容存到ch 缓存区内。ch 即上述的四行八列信息,接着设置一个窗口,并纵向写上1,2,3,4,然后用movetext(),将此窗口内容复制到另一区域,由于此区域包括背景色和显示的字符,所以被复制到另一区域的内容也是相同的背景色和文本。当按任一键后,又出现提示信息,再按键,则存在ch缓冲区内的文本由puttext()又复制到开设的窗口内了,注意上述的函数movetext(),gettext(),puttext()均与开设的窗口内坐标无关,而是以整个屏幕为参考系的。
#include
#include
void main()
{
int i;
char ch[4*8*2]; /* 定义ch 字符串数组作为缓存区*/
textmode(C80);
textbackground(BLUE);
textcolor(RED);
clrscr();
gotoxy(10,10);
cprintf("L:load");
gotoxy(10,11);
cprintf("S:save");
gotoxy(10,12);
cprintf("D:delete");
gotoxy(10,13);
cprintf("E:exit\r\n");
cprintf("Press any key to continue");
getch();
gettext(10,10,18,13,ch); /* 存矩形区文存到ch 缓存区*/
clrscr();
textbackground(1);
textcolor(3);
window(20,9,34,14); /* 开一个窗口*/
clrscr();
cprintf("1.\r\n2.\r\n3.\r\n4.\r\n");/* 纵向写1,2,3,4 */
movetext(20,9,34,14,40,10); /* 将矩形区文本复制到另一区域*/
puts("hit any key");
getch();
clrscr();
cprintf("press any key to put text");
getch();
clrscr();
puttext(23,10,31,13,ch); /* 将ch 缓存区所存文本在屏上显示*/
getch();
}
5)状态查询函数
有时需要知道当前屏幕的显示方式,当前窗口的坐标、当前光标的位置,文本的显示属性等,Turbo C 提供了一些函数得到屏幕文本显示有关信息的函数:
void gettextinfo(struct text_info *f);
这里的text_info 是在conio.h 头文件中定义的一个结构,该结构的定义是
struct text_info
{
unsigned char winleft; /* 窗口左上角x 坐标*/
unsigned char wintop; /* 窗口左上角y 坐标*/
unsigned char winright; /* 窗口右下角x 坐标*/
unsigned char winbottom; /* 窗口右下角y 坐标*/
unsigned char attributes; /* 文本属性*/
unsigned char normattr; /* 通常属性*/
unsigned char currmode; /* 当前文本方式*/
unsigned char screenheight; /* 屏高*/
unsigned char screenwidth; /* 屏宽*/
unsigned char curx; /* 当前光标的x 值*/
unsigned char cury; /* 当前光标的y 值*/
};
例8.7 下面的程序将屏幕设置成80列彩色文本方式,并开了一个window(1,5,70,20)的窗口,在窗口中显示了current information of window,然后用gettextinfo 函数得到当前窗口的信息,后面的cprintf()函数将分别显示出结构text_info 各分量的数值来。
#include
#include
void main()
{
struct text_info current;
textmode(C80);
textbackground(1);
textcolor(13);
window(1,5,70,20);
clrscr();
cputs("Current information of window\r\n");
gettextinfo(¤t);
cprintf("Left corner of window is %d,%d ",current.winleft,current.wintop);
cprintf("Right corner of window is %d,%d ",
current.winright,current.winbottom);
cprintf("Text window attribute is%d ",current.attribute);
cprintf("Text window normal attribute is %d ",current.normattr);
cprintf("Current video mode is %d ",current.currmode);
cprintf("Window height and width is %d,%d ",current.screenheight,current.screenwidth);
cprintf("Row cursor pos is %d,Column pos is %d ",current.cury,current.curx);
getch();
}
8.2.2 键盘输入
当我们在键盘上按下某键时,系统是怎样知道是哪一个键被按下呢?它的奥妙在于计算机键盘内有一个微处理器,它用来扫描和检测每个键的按下和拾起状态。然后以程序中断的方式(INT 9)与主机通信。ROM 中BIOS 内的键盘中断处理程序,会将一个字节的按键扫描码(扫描码的0~6 位标识了每个键在键盘上的位置,最高位标识按键的状态,0 对应该键是被按下;1 对应松开。它并不能区别大小写字母,而且一些特殊键如PrintScreen 等不产生扫描码直接引起中断调用)翻译成对应的ASCII 码。
由于ASCII 码仅有256 个,它不能将PC 键盘上的键全部包括,因此有些控制键如CTRL,ALT,END,HOME,DEL 等用扩充的ASCII 码表示,扩充码用两个字节的数表示。第一个字节是0,第二个字节是0~255 的数,键盘中断处理程序将把转换后的扩充码存放在Ax 寄存器中,存放格式如表8-3 所示。对字符键,其扩充码就是其ASCII 码。
键名
AH
AL
字符键
扩充码=ASCII 码
ASCII 码
功能键/组合键
扩充码
0
表8-3 键盘扫描码
是否有键按下,何键按下,简单的应用中可采用两种
办法
鲁班奖评选办法下载鲁班奖评选办法下载鲁班奖评选办法下载企业年金办法下载企业年金办法下载
:一是直接使用Turbo C 提供的键盘操作函数bioskey()来识别,二是通过int86()函数,调用BIOS的INT 16H,功能号为0 的中断。它将按键的扫描码存放在Ax 寄存器的高字节中。
函数bioskey()的原型为:
int bioskey(int cmd);
它在bios.h 头文件中进行了说明,参数cmd 用来确定bioskey()如何操作,其含义见表8-4。
cmd
操作
0
bioskey()返回按健的键值,该值是2 个字节的整型数。若没有键按下,则该函数一直等待,直到有键按下。当按下时,若返回值的低8 位为非零,则表示为普通键,其值代表该键的ASCII 码。若返回值的低8 位为0,则高8 位表示为扩展的ASCII码,表示按下的是特殊功能键。
1
bioskey()查询是否有键按下。若返回非0 值,则表示有键按下,若为0 表示没键按下。
2
bioskey()将返回一些控制键是否被按过,按过的状态由该函数返回的低8 位的各位值来表示:
字节位
对应的16 进制数
含义
0
0x01
右边的shift 键被按下
1
0x02
左边的shift 键被按下
2
0x04
Ctrl键被按下
3
0x08
Alt 键被按下
4
0x10
Scroll Lock 已打开
5
0x20
Num Lock 已打开
6
0x40
Caps Lock 已打开
7
0x80
Insert 已打开
表8-4 函数bioskey(int cmd)中 cmd的取值及含义
当某位为l 时,表示相应的键已按,或相应的控制功能已有效,如选参数cmd 为2,若key 值为0x09,则表示右边的shift 键被按,同时又按了Alt 键。
函数int86 ()的原型为:
int int86(int intr_num,union REGS *inregs,union REGS *outregs);
这个函数在bios.h 头文件中进行了说明,它的第一个参数intr_num 表示BIOS 调用类型号,相当于int n 调用的中断类型号n,第二个参数表示是指向联合类型REGS 的指针,它用于接收调用的功能号及其它一些指定的入口参数,以便传给相应的寄存器,第三个参数也是一个指向联合类型REGS 的指针,它用于接收功能调用后的返回值,即出口参数,如调用的结果,状态信息,这些值从相关寄存器中得到。
例8.8 bioskey()函数的使用。
#include
#include
void main()
{
int k1,k2,k;
do
{
k=bioskey(0);
k1=k & 0x00FF; /*得到低8位的值*/
k2=k >> 8; /*得到高8位的值*/
switch(k2)
{
case 71:
printf("你按下了Home键!\n");
break;
case 79:
printf("你按下了End键!\n");
break;
case 73:
printf("你按下了PgUp键!\n");
break;
case 81:
printf("你按下了PgDn键!\n");
break;
default:
printf("你按下了Home,End,PgUp,PgDn之外的键!\n");
}
}while(k1!=27);
/*ESC键才退出*/
}
8.3 图形程序设计
计算机图形程序设计是程序设计中比较难,但又吸引人的部分。因在ANSI C中没有对图形库的要求,所以不同版本的C语言编译程序提供的图形函数不一样。本节以Turbo C的图形库来介绍图形程序设计。
8.3.1 图形模式的初始化
要进行图形程序设计,就要将屏幕显示模式设置为图形模式。要设置图形模式,可以用Turbo C提供的图形初始化函数:
void far initgraph(int far *gdriver,int far *gmode,char far *pathtodriver);
其中,gdriver表示图形驱动器,gmode表示图形模式,pathtodriver表示图形驱动程序所在的目录路径,若图形驱动程序在Turbo C的默认目录下,可将参数pathtodriver设置为空字符串(" ")。
若不知道所用的图形显示器适配器的种类,在调用图形初始化函数时,设置参数gdriver的值为0或DETECT,由系统自动进行硬件检测。Turbo C提供的图形驱动器、模式的符号常量及数值的意义参见Turbo C提供的graphics.h文件。
例如在程序的开始部分可进行如下的描述来自动检测图形显示器适配器。
#include
#include
void main( )
{
int gd=DETECT,gm; /* 自动检测 */
initgraph(&gd,& gm, " "); /* 图形模式初始化 */
……
}
Turbo C提供的图形与字形驱动文件见表8-5
文件名
用途
ATT.BGI
AT&T图形驱动文件
CGA.BGI
CGA图形驱动文件
EGAVGA.BGI
EGA和VGA图形驱动文件
IBM8514.BGI
IBM8514图形驱动文件
GOTH.CHR
歌特笔划字形文件
LITT.CHR
小号笔划字形文件
SANS.CHR
无衬线矢量笔划字形文件
TRIP.CHR
三重矢量笔划字形文件
表8-5 Turbo C 图形与字形驱动文件表
8.3.2 图形模式下的坐标系
在图形模式下,屏幕上每个像素的显示位置用点坐标来描述。图形模式下的坐标系如图8-5所示。坐标原点在屏幕左上角,其坐标为(0,0),X轴为水平方向,Y轴为垂直方向。
图 8-5 图形模式下的坐标系
8.3.3 屏幕图形的色彩与相关操作
在C语言中,图形模式的屏幕颜色设置分为背景色的设置和作图色的设置。背景色的设置通过对函数setbkcolor (int color ), 作图色的设置通过对函数setcolor (int color )的调用来实现。其中int color为整型数据,取值范围为0~15,数字的表示的颜色见表8-2。
背景和前景色可以有16种颜色。(这种方式成为调色板方式)
图形屏幕相关的操作的主要函数如表8-6所示:
函数
功能
cleardevice( )
清除图形屏幕
clearriwport( )
清除当前视区
setbkcolor( )
设置图形背景颜色
Setcolor( )
设置图形前颜色
setfillstyle( )
设置填充模式和填充颜色
settextstyle( )
设置文本字符的显示模式
closegraph( )
关闭图形系统,返回文本方式
表8-6 屏幕图形的色彩与相关操作的函数
几个函数的说明如下:
(1)setfillstyle()为填充函数,其作用是对图形内部填充颜色,函数原型为:
void far setfillstyle(int pattern, int color);
其中,pattern表示填充模式,color表示颜色参数。
pattern的取值见表8-7
符号
数值
描述
EMPTY_FILL
0
用背景色填充
SOLID_FILL
1
单色填充
LIN_FILL
2
用一填充
LTSLASH_FILL
3
用///填充
SLASH_FILL
4
用粗\\\填充
BKSLASH_FILL
5
用粗///填充
LIBKSLASH_FILL
6
用\\\填充
HATCH_FILL
7
用淡影线填充
XHATCH_FILL
8
用交叉线填充
INTERLEAVE_FILL
9
用间隔线填充
WIND_EDOT_FILL
10
用稀疏空白点填充
CLOSEDOT_FILL
11
用密集空的点填充
USER_FILL
12
用户定义的填充模式
表8-7 图形填充模式表
(2)settextstyle()函数,用于设置文本字符串的字型、输出方向和字符大小。函数原型为:
void far settextstyle(int font, int direction, int charsize);
其中,font用于设置字符的字体,其取值见表8-8
direction用于设置字符输出方向,其取值见表8-9
charsize用于设置字符大小,其取值见表8-10
符号常量
数值
含义
DEFAULT_FONT
0
8*8点阵字(缺省值)
TRIPLEX_FONT
1
3倍笔划字体
SMALL_FONT
2
小号笔划字体
SANS_SERIF_FONT
3
无衬线笔划字体
GOTHIC_FONT
4
黑体笔划字体
表8-8 font的取值
符号常量
数值
含义
HORIZ_DIR
0
从左到右
VERT_DIR
1
从底到顶
表8-9 direction的取值
符号常量或数值
含义
符号常量或数值
含义
1
8*8点阵
7
56*56点阵
2
16*16点阵
8
64*64点阵
3
24*24点阵
9
72*72点阵
4
32*320点阵
10
80*80点阵
5
40*40点阵
USER_CHAR_SIZE=0
用户定义的字符大小
6
48*48点阵
表8-10 charsize的取值
8.3.4 基本绘图函数
图形由点、线、面组成,Turbo C 提供了一些函数,以完成这些操作,而所谓面则可由对封闭图形填上颜色来实现。
1) 画点函数
void far putpixel(int x,int y,int color);该函数表示在指定的x,y 位置画一点,点的显示颜色由设置的color值决定,关于颜色的设置,将在设置颜色函数中介绍。
int far getpixel(int x,int y);该函数与putpixel()相对应,它得到在(x,y)点位置上的象素的颜色值。
例8.9:下面是一个画点的程序,它将在y=20 的恒定位置上,沿x 方向从x=200 开始,连续画两个点(间距为4 个象素位置),又间隔16 个点位置,再画两个点,如此循环,直到x=300 为止,每画出的两个点中的第一个由putpixel(x,20,1)所画,第二个则由putplxel(x+4,20,2)画出,颜色值分别设为1 和2。
#include
void main()
{
int graphdriver=DETECT, graphmode,x;
initgraph(&graphdriver,&graphmode,"");
cleardevice();
for(x=20;x<=300;x+=16)
{
putpixel(x,20,1);
putpixel(x+4,20,2);
}
getch();
closegraph();
}
2) 有关画图坐标位置的函数
在屏幕上画线时,如同在纸上画线一样。画笔要放在开始画图的位置,并经常要抬笔移动,以便到另一位置再画。我们也可想象在屏上画图时,有一无形的画笔,可以控制它的定位、移动(不画),也可知道它能移动的最大位置限制等。完成这些功能的函数是:
void far moveto(int x,int y);移动画笔到指定的(x,y)位置,移动过程不画。
void far moverel(int dx,int dy);画笔从现行位置(x,y)处移到一位置增量处(x+dx,y+dx),移动过程不画。
int far getx(void);得到当前画笔的x 位置。
int far gety(void);得到当前画笔的y 位置
3) 画线函数
这类函数提供了从一个点到另一个点用设定的颜色画一条直线的功能,起始点的设定方法不同,因而有下面不同的画线函数:
void far line(int x0,int y0,int x1,int y1);从(x0,y0)点到(x1,y1)点画一直线。
void far lineto(int x,int y);从现行画笔位置到(x,y)点画一直线。
void far linerel(int dx,int dy);从现行画笔位置(x,y)到位置增量处(x+dx,y+dy)画一直线。
例8.10:下面的程序将用moveto 函数将画笔移到(100,20)处,然后从(100,20)到(100,80)用1ineto 函数画一直线。再将画笔移到(200,20)处,用lineto 画一直线到(100,80)处,再用line 函数在(100,90)到(200,90)间连一直线。接着又从上次1ineto 画线结束位置开始(它是当前画笔的位置),即从(100,80)点开始到x 增量为0,y 增量为20 的点(100,100)为止用linerel 函数画一直线。moverel(-100,0)将使画笔从上次用1inerel(0,20)画直线时的结束位置(100,100)处开始移到(100-100,100-0),然后用linerel(30,20)从(0,100)处再画直线至(0+30,100+20)处。
用line 函数画直线时,将不考虑画笔位置,它也不影响画笔原来的位置,lineto 和1inerel要求画笔位置,画线起点从此位置开始,而结束位置就是画笔画线完后停留的位置,故这两个函数将改变画笔的位置。
#include
void main()
{
int graphdriver=DETECT,graphmode;
initgraph(&graphdriver,&graphmode,"");
cleardevice();
moveto(100,20);
lineto(100,80);
moveto(200,20);
lineto(100,80);
line(100,90,200,90);
linerel(0,20);
moverel(-100,0);
linerel(30,20);
getch();
closegraph();
}
4) 画矩形和条形图函数
画矩形函数rectangle 将画出一个矩形框,而画条形函数bar 将以给定的填充模式和填充颜色画出一个条形图,而不是一个条形框,关于填充模式和颜色将在后面介绍。
void far rectangle(int xl,int y1,int x2,int y2);该函数将以(x1,y1)为左上角,(x2,y2)为右下角画一矩形框。
void bar(int x1,int y1,int x2,int y2);该函数将以(xl,y1)为左上角,(x2,y2)为右下角画一实形条状图,没有边框,图的颜色和填充模式可以设定。若没有设定,则使用缺省模式。
5) 画椭圆、圆和扇形图函数
在画图的函数中,有关于角的概念。在Turbo C 中是这样规定的:屏的x 轴方向为0 度,当半径从此处逆时针方向旋转时,则依次是90 度、180 度、270 度,当360 度时,则和x轴正向重合,即旋转了一周。如图8-6 所示。
图8-6 起始角和终止角
void ellipse(int x,int y,int stangle,int endangel,int xradius,int yradius);该函数将以(x,y)为中心,以xradius 和yradius 为x 轴和y 轴半径,从起始角stangle开始到endangle 角结束,画一椭圆线。当stangle=0,endangle=360 时,则画出的是一个完整的椭圆,否则画出的将是椭圆弧。关于起始角和终止角规定如图8-2 所示。