17.1 为什么直到程序结束时才看到屏幕输出?
有时,依赖于所使用的编译程序和操作系统,系统会对输出进行缓冲。“缓冲”是指任何要送到设备上的输出,无论设备是屏幕、磁盘还是打印机,都被存储起来,直到输出量大到足以进行高效的输出。当存储了足够多的输出信息时,再整块地向指定的设备输出。
这种过程会给不了解其作用的程序员带来两个问题。首先,在程序送出输出内容后,它可能要再过一段时间后才会在屏幕上显示出来。如果程序员正在试图跟踪程序的当前运行状态,他就会被这种效果所困扰。
其次,更可怕的是,在程序显示提示信息并等待用户输入时,很可能就会发生问题。当程序试图从用户那里得到输入信息时,输出缓冲区可能还未被“填满”,因此送往屏幕的提示信息可能不会显示出来,用户也就不知道程序已经在等待他进行输入了——他所能得出的结论只能是这个“可爱”的程序突然停止工作了。
如何解决这个问题呢?有两种办法。第一种办法是在程序的开始部分,在进行任何输出之前,加入下述语句:
setvbuf(stdout,NULL,_IONBF,O);
该语句的作用是实现程序到屏幕的无缓冲输出。当这条命令被执行后,每一个被送往屏幕的字符都会立即显示出来。
用这种办法解决这个问题确实比较方便,但是还不够理想。笔者不想在这里对屏幕输入和输出展开一次技术讨论,但笔者要指出这样一点,即对屏幕输出进行缓冲是有充分的理由的,并且你还会希望这样做。
这佯一来,就引出了解决输出缓冲问题的另一种办法。当fflush()命令作用于一个输出缓冲区时,它会使该缓冲区“倒空”自身,而不管它是否已被填满。因此,为了解决屏幕缓冲问题,在需要“倒空”输出缓冲区时,你只需插入如下命令:
fflush(stdout):
在程序要求用户输入之前,或者在程序开始一项耗时的大型的计算工作之前,最好先“倒空”输出缓冲区。这样,当程序暂时停住时,你就能清楚地知道其原因了。
17.2 怎样在屏幕上定位光标?
C标准并没有提供在屏幕上定位光标的方法,其原因很多。C被设计成能在各种各样的计算机上工作,而其中的许多机型都有不同的屏幕类型。例如,在行式打印终端上,不能向上移动光标;一个嵌入式系统甚至也可能是用c编写的,而在它的应用场合可能根本就没有屏幕。
尽管这样,在屏幕上定位光标对你的程序来说还是有用的。你可能希望给用户一个吸引人的视觉效果,并且只能通过移动光标来实现;你还可能想用相应的输出命令尝试一点动画效果。尽管这方面没有标准的处理方法,但还是有好几种方法可以解决这个问题。
首先,编译程序的开发者会提供一个函数库,专门处理基于他们的编译程序的屏幕输出操作,其中肯定会有定位光标的函数。但是,很多人认为这是最差的解决办法,因为每一个开发商都可以自由地开发自己的实现方法,所以在一种编译程序上开发的程序,当移到另一种编译程序上时,几乎必然要重写,更别说移到另一种计算机上了。
其次,可以定义一套标准的库函数,并使编译程序的开发者在他的编译程序中实现这套函数。流行的Curses软件包就起源于这种思路。在大多数计算机和编译程序中都可以使用Curses,因此,用Curses实现屏幕输出的程序在大多数计算机和编译程序中都可以工作。
第三,你可以利用这样一个事实,即你想打印到其上的设备会用一种特定的方式解释你送过去的字符。终端(或屏幕)应设计成按一种标准方式去解释送给它们的字符,这就是ANSI标准。如果你认为你的计算机是遵循ANSI标准的,你就可以通过打印相应的字符来控制屏幕把光标定位在所需的位置上,并且可以把这种操作和其它操作组合在一起。
17.3 向屏幕上写数据的最简单的方法是什么?
C语言包含了大约几百个向屏幕上写数据的函数,很难决定在某一时刻最适合用哪一个函数来向屏幕上写数据。许多程序员只是简单地选择一个或两个打印函数,并且以后只使用这些函数。这是一种可以接受的编程风格,尽管这样的程序员也许不是总能写出最好的代码。
一个程序员应该做的就是把每个打印函数的设计目的和最佳用法都回顾一遍,这样,当他需要向屏幕上打印数据时,他就能选出最佳的函数,甚至还可以自己编写一些打印函数。
要成为一个真正熟练的程序员,第一步要做的工作的一部分就是学会正确地使用标准C语言库中的打印函数。让我们仔细地分析一下其中的几个函数。
printf(<format string>,variables);
printf()是使用最广泛的打印函数。在把文本输出到屏幕上时,有些程序员只使用这个函数。尽管如此,该函数的设计目的只是用来把带格式的文本打印到屏幕上。实际上,“printf\"是“print formatted(带格式打印)”的缩写。带格式的文本是指文本中不仅仅包含写到代码中的字符串,还包含由程序动态生成的数字、字符和其它数据;此外,它的内容可以按一种特定的方式显示,例如,它可以按所指定的小数点前后的位数来显示实数。正是由于这个原因,所以
printf()函数是不可缺少的!
那么,为什么有时又不使用printf()呢?这里有几个原因。
第一个原因是程序员想更清楚地表达他的意图。程序员可能只对printf()函数提供的诸多功能中的一小部分感兴趣,在这种情况下,他可能想使用只提供这一小部分功能的那个函数,例如:
putchar(char);
该函数的作用是把一个字符送到屏幕上。如果你只需做这部分工作,那么它是十分合适的。除此之外,它就不见得有什么好处了。然而,通过使用这个函数,你就能非常清楚地表达相应的那部分代码的意图,即把单个字符送到屏幕上。
puts(char*);
该函数的作用是把一个字符串写到屏幕上。它不能象printf()一样接受额外的数据,也不能对传递过来的字符串加以处理。同样,通过使用这个函数,你就能非常清楚地表达相应的那部分代码的意图。
程序员不使用printf()的第二个原因是为了提高程序的执行效率。printf()函数的额外开销太多,也就是说,即使是进行一次简单的操作,它也需要做大量的工作。它需要检查传递过来的字符串与格式说明符是否匹配,还需要检查传递过来的参数个数,等等。上面提到过的另外两个函数没有这些额外的开销,因此它们可以执行得非常快。这个因素对大多数向屏幕上写数据的程序来说并不重要,但是,在处理磁盘文件中的大量数据时,它就显得很重要了。
不使用printf()的第三个原因是程序员想减小可执行程序的大小。当你在程序中使用了标准C函数时,它们必须被“连接进来”,也就是说,它们必须被包含进所生成的可执行文件中。对于象putchar()和puts()这样简单的打印函数,对应的程序段是很短的,而对应于printf()的程序段却相当长——特别是因为它必然要包含前两个函数。
第二个原因可能是最不重要的一个原因,然而,如果你在使用静态连接程序,并且想保持较小的可执行文件的话,那么这就是一项重要的技巧了。例如,尽可能减小TSR和其它一些程序的大小是很值得的。
无论如何,程序员都应根据自己的目的来选择需要使用的函数。
17.4 向屏幕上写文本的最快的方法是什么?
通常,你不会过分关心程序写屏幕的速度。但是,在有些应用中,需要尽可能快地写屏幕,这样的程序可能包括:
·文本编辑器。如果不能很快地写屏幕,则由用户输入文本所造成的屏幕滚动和其它有关操作可能会显得太慢。
·活动的文本。在同一区域快速地打印字符,是获得动画效果的一种常用手段,如果不能快速地把文本打印到屏幕上,那么动画就太慢了,视觉效果就不会好。
·监视器程序。这样的程序要连续地监视系统、其它程序或硬件设备,它可能需要每秒在屏幕上打印多次状态的更新信息,而通过标准c库函数实现的屏幕打印对这样的程序来说很可能显得太慢。
那么,在这些情况下应该怎么办呢?有三种办法可以加快程序写屏幕的速度:选用额外开销较小的打印函数;使用提供了快速打印功能的软件包或函数库;跳过操作系统,直接写屏幕。下面将按从简到繁的顺序分析这几种办法。
选用额外开销较小的打印函数
有些打印函数的额外开销比别的打印函数要多。“额外开销”是指与其它函数相比,某个函数必须做的额外工作。例如,printf()的额外开销就比puts()多。那么,为什么会这样呢?
puts()函数是很简单的,它接受一个字符串并把它写到显示器屏幕上。当然,printf()函数也能做同样的工作,但它还要做大量其它的工作——它要分析送给它的字符串,以找出指示如何打印内部数据的那部分特殊代码。
也许你的程序中没有特殊字符,而且你也没有传递任何这样的字符,但不幸的是,printf()无法知道这一点,它每次都必须检查字符串中是否有特殊字符。
函数putch()和puts()之间也有一点微小的差别——在只打印单个字符时,putch()的效果更好(额外开销更少)。
遗憾的是,与真正把字符写到屏幕上所带来的额外开销相比,这些C函数本身的额外开销是微不足道的。因此,除了在一些特殊情况下之外,这种办法对程序员不会有太大的帮助。
使用提供了快速打印功能的软件包或函数库
这可能是有效地提高写屏速度的最简单的办法。你可以得到这样的一个软件包,它或者会用更快的版本替换编译程序中固有的打印函数,或者会提供一些更快的打印函数。
这种办法使程序员的工作变得十分轻松,因为他几乎不需要改动自己的程序,并且可以使用别人花了大量时间优化好了的代码。这种办法的缺点是这些代码可能属于另一个程序员,在你的程序中使用它们的费用可能是昂贵的。此外,你可能无法把你的程序移植到另一种平台上,因为那种平台上可能没有相应的软件包。
不管怎样,对程序员来说,这是一种既实用又有效的办法。
跳过操作系统,直接写屏幕
由于多种原因,这种办法有时不太令人满意。事实上,这种办法在有些计算机和操作系统上根本无法实现。此外,这种办法的具体实现通常会因计算机的不同而不同,甚至在同一台计算机上还会因编译程序的不同而不同。
不管怎样,为了提高视频输出速度,直接写屏是非常必要的。对全屏幕文本来说,你可能可以每秒种写几百屏。如果你需要这样的性能(可能是为了视频游戏),采用这种办法是值得的。
因为每种计算机和操作系统对这个问题的处理方法是不同的,所以要写出适用于所有操作系统的程序是不现实的。下文将介绍如何用Borland c在MS-DOS下实现这种办法。即使你不使用这些系统,你也应该能从下文中了解到正确的方法,这样你就可以在你的计算机和操作系统上写出类似的程序了。
首先,你需要某种能把数据写到屏幕上的方法。你可以创建一个指向视频缓冲区的指针。在MS-DOS下使用Borland C时,可以用下述语句实现这一点:
char far*Sereen=MK_FP(0xb800,Ox0000);
far指针所指向的地址并不局限于程序的数据段中,它可以指向内存中的任何地方。MK_FP()产生一个指向指定位置的far指针。有些其它的编译程序和计算机并不要求区分指针的类型,或者没有类似的函数,你应该在编译程序手册中查找相应的信息。
现在,你有了一个“指向”屏幕左上角的指针。只要你向该指针所指向的内存位置写入若干字节,相应的字符就会从屏幕的左上角开始显示。下面这个程序就是这样做的:
#include<dos.h>
main()
{
int a:
char far*Screen=MK_FP(Oxb800。Ox0000):
for(a=0;a<26;++a)
sereen[a*2]=\'a\'+a:
return(O);
}
该程序运行后,屏幕顶端就会打印出小写的字母表。
你将会发现,字符在视频缓冲区中并不是连续存放的,而是每隔一个字节存放一个。这是为什么呢?这是因为一个字符虽然仅占一个字节,但紧接着它的下一个字节要用来存放该字符的颜色值。因此,屏幕上显示的每个字符在计算机内存中都占两个字节:一个字节存放字符本身,另一个字节存放它的颜色值。
这说明了两点:首先,必须把字符写入内存中相隔的字节中,否则你将会只看到相隔的字符,并且带有古怪的颜色。其次,如果要写带颜色的文本,或者改变某个位置原有的颜色,你就需要自己去写相应的颜色字节。如果不这样做,文本仍然会按原来的颜色显示。每个描述颜色的字节既要描述字符的颜色(即前景色),又要描述字符的背景色。一共有16种前景色和16种背景色,分别用颜色字节的低4位和高4位来表示。
这部分内容对一些缺乏经验的程序员来说可能有点复杂,但还是比较容易理解的。只要记住有16种颜色,其编号范围是从。到15,要得到颜色字节的值,只需把前景色的值和背景色值的16倍相加即可。下面这个程序就是这样做的: