Chinaunix首页 | 论坛 | 博客
  • 博客访问: 19280019
  • 博文数量: 7460
  • 博客积分: 10434
  • 博客等级: 上将
  • 技术积分: 78178
  • 用 户 组: 普通用户
  • 注册时间: 2008-03-02 22:54
文章分类

全部博文(7460)

文章存档

2011年(1)

2009年(669)

2008年(6790)

分类: C/C++

2008-05-30 18:12:05

一个实用的程序必须通过某种手段把它的结果或需求转达给用户。为了实现这种与用户之间的交流,C语言提供了一个内容丰富的函数库,即标准输入/输出库。本章的内容就是针对这些函数的,并回答了有关它们的一些常见问题。

    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(,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
    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倍相加即可。下面这个程序就是这样做的:

#include
    main()
    {
       int fc,bc,c;
       scanf(\"%d %d\",&fc,&bc);
       printf(\"Foreground=%d,Background=%d,Color=%d\\n\",
       fc,bc,fc+bc*16);
       return(0);
    }
    你可能会同意这样一点,即在大多数情况下,在整个程序中都由程序员明确地写出要送到屏幕上的字节是不现实的。最好是编写一个把文本写到屏幕上的函数,然后频繁地调用这个函数。让我们来分析一下如何构造这样一个函数。
    首先,你需要问一下自己:“我需要向这个通用打印函数传递一些什么信息?”作为初学者,你可以传递以下这些信息:
    ·要写到屏幕上的文本;
    ·文本的位置(两个坐标值)
    ·字符的前景色和背景色(两个值)
    现在,你知道了需要把什么数据传递给该函数,因此你可以按以下方式来说明这个函数:
    void PrintAt(char*Text,int x,int y,int bc,intfc)
    下一步你需要计算要打印的文本的颜色字节值:
    int Color=fc+be*16:
    然后需要计算文本指针的起始位置:
    char far*Addr=&screen[(x+y*80)*2];
    需要特别注意的是,为了把文本写到正确的位置上,你必须把偏移量乘以2。此外,使用该语句的前提是在程序中已经定义了变量Screen。如果该变量还未定义,你只需在程序中相应的位置插入下述语句:
    char far*Screen=MK_FP(0xb800,0x0000);
    现在,准备工作都完成了,你可以真正开始向屏幕上写文本了。以下是完成这项任务的程序段:
    while(*Text)
    {
       *(Addr++)=*(Text++);
       *(Addr++)=Color;
    }
    在还未写完全部文本之前,这段代码会一直循环下去,并把每个字符和对应的颜色写到屏幕上。
    以下这个程序中给出了这个函数完整的代码,并且调用了一次该函数。
    #include(dos.h>
    /*This is needed for the MK—FP function*/
    char far*Screen=MK_FP(Oxb800,Ox0000):
    void PrintAt(char*Text,int x,int y,int bc,int fc)
    {
       int Color=fc+bc*16;
       char far*Addr=&screen[(x+y*80)*2];
       while(*Text)
       {
          *(Addr++)=*(Text++);
          *(Addr++)=Color;
       }
    }
    main()
    {
       int a:
       for(a=1;a<16:++a)
       PrintAt(\"This is a test\",a,a,a+1,a);
       return(0);
    }
    如果比较一下这个函数和固有的打印函数的执行时间,你会发现这个函数要快得多。如果你在使用其它硬件平台,你可以用这里所提供的思路来为你的计算机和操作系统编写一个类似的快速打印函数。

    17.5  怎样防止用户用Ctrl+Break键中止程序的运行?
    在缺省情况下,MS—DOS允许用户按Ctrl+Break键来中止程序的运行。在大多数情况下,这是一种很有用的功能,它使用户能从程序不允许退出的地方退出程序,或者从一个运行已经失常的程序中退出。

  但是,在某些情况下,这种操作是非常危险的。有些程序一旦被中止,可能就会采取“保护”措施,从而使用户能侵入保密数据区。此外,如果程序在更新磁盘上的数据文件时被中止,很可能就会毁坏数据文件,从而毁掉一些有用的数据。
    基于这些原因。在某些程序中,解除Break键的功能是很有必要的。警告:在不能百分之百地肯定这样的代码能起作用之前,不要轻易把它加到你的程序中去!否则,一旦这段代码有误并且程序在运行时阻塞住,你就不得不重新启动计算机,而这很可能会毁掉最近对程序所作的修改。
    下面介绍如何使Break键失效。这是一种特殊的操作,在有些计算机上无法实现,而有些计算机上根本就没有Break键。因此,c语言中没有一条特殊命令用来解除Break键的功能,而且,即使在以MS:DOS为操作系统的计算机上,也没有一种标准的方法来实现这一点。在大多数计算机上,你必须用一条特殊的机器语言命令来实现这一点。下面是一个在MS-DOS中解除Break键功能的函数:
    #include
    void StopBreak()
    {
       union REGS in,out;
       in.x.ax=0x3301:
       in.x.dx=O:
       int86(0x21,&in,&out);
    }
    这个函数要设置一组寄存器,即把3301H赋给ax寄存器,把O赋给dx寄存器。然后,它将通过这些寄存器调用中断21H,从而调用DOS,并通知它不再希望让Break键中止程序的运行。
  下面是一个用来测试该函数的程序:
  #include
  #include
  void StopBreak()
  {
    union REGS in,out:
    in.x.ax=Ox3301:
    in.x.dx=O:
    int86(Ox21,&in,&out):
  }
  int main()
  {
    int a:
    long b:
    StopBreak();
    for(a=O;a<100;++a)
    {
       StopBreak();
       printf(\"Line %d.\\n\",a);
       for(b=O;b<500000L;++b);
    }
    return O:
  }

    17.6  怎样才能只得到一种特定类型的数据,例如字符型数据?
    与几乎所有有关计算机科学的问题一样,这个问题的答案也依赖于你要做什么。例如,如果你要从键盘上读入字符,你可以使用scanf():   
  scanf(\"%C\",&c);
  此外,你也可以使用一些现成的C库函数:
    c=getchar();
    这些方法所产生的结果基本上都一样,只不过使用scanf()能为程序员提供更多的安全性检查。
    如果要接收其它类型的数据,有两种方法可供使用。你可以逐个字符地读入数据,并且每次都检查读入的数据是否正确。你也可以使用scanf(),并通过检查其返回值来确定读入的数据是否都正确。
    你可以用第二种方法简单而高效地读入一串记录,并检查它们是否都正确。下例就实现了这一点:
    #include
    main()
    {  
       int i,a,b:
       char c;
       void ProcessRecord(int,int,char);
       for(i=O;i       {
          if(scanf(\"%d%d%c\",&a,&b,&c)!=3)
             printf(\"data line %d is in error.\\n\");
          else
             ProcessRecord(a,b,c);
       }  
       return(O);
    }17.7  为什么有时不应该用scanf()来接收数据?
    尽管在读取键盘输入时,scanf()是用得最多的函数,但有时最好还是不使用scanf()。这些情况可以分为以下几类:
    必须立刻处理用户所击的键

    如果你的程序要求一旦某键被按下,就要立刻做出反应,那么scanf()就没有用了。scanf()至少要等到Enter键被按下后才会做出反应,而你根本不知道用户什么时候才会按下Enter键——也许是一秒钟以后,也许是一分钟以后,也许是一个世纪以后。
    尽管在实时程序中,例如在计算机游戏中,用scanf()来读取键盘输入是很糟糕的,然而在通用的实用程序中,这同样也是很糟糕的。例如,在操作一个由字母组成的菜单时,用户肯定喜欢只按a键,而不是按完a键后再按一下Enter键。
    遗憾的是,标准C函数库中并没有能立刻响应用户击键的函数,因此你只能依靠辅助函数库或编译程序所带的一些特殊函数。

    当scanf()对输人进行分析时,你所需要的数据可能会被忽略掉

    scanf()是一个很精明的函数——有时精明得过分了。为了满足用户对输入数据的要求,scanf()会跳行,会丢掉不合适的数据,并且会忽略空白符。    

然而,有时你并不希望scanf()精明到这种程度!有时你想把用户所键入的内容全部看作是输入,不管它是太多还是太少。一个不适合使用scanf()的例子就是要从用户那里接受文本态命令的程序一一事先你并不知道用户要键入的句子中会有多少个单词。在这种情况下,就不能使用scanf(),因为你不知道用户什么时候才会按下Enter键。

    预先不知道用户会输人哪种类型的数据

    有时,你已经准备好要接受用户的输入,但你不知道用户将输入一个数字,还是一个词,或者是某个特殊的字符。在这种情况下,你必须按某种中性格式,例如字符串,来接受用户的输入,并且在继续下一步操作之前判断输入的数据是哪一种类型。
    此外,scanf()还会带来这样一个问题,即它会把不合适的输入保存在输入缓冲区中。例如,如果你期望读入一个数字,而用户却输入了一个字符串,那么有关的代码可能会无限循环下去,因为它试图把这个字符串当作一个数字来分析。下面这个程序演示了这一点:
    #include
    main()
    {
       int i;
       while(scanf(\"%d\",&j)==O)
       {
         print{(\"Still looping.\\n\");
       }
       return(O);
    }
    如果你象程序所期望的那样输入了一个数字,那么这个程序完全能正常运行。但是,如果你输入了一个字符串,那么这个程序就会无限循环下去。

    17.8  怎样在程序中使用功能键和箭头键?
    在程序中使用功能键和箭头键可以使程序更容易使用。箭头键可用来移动光标,而功能键使用户能做一些特殊的事情,还可用来替代一些经常要键入的字符序列。
    然而,与其它“特殊”功能一样,C语言本身并没有提供读入功能键和箭头键的标准方法。用scanf()来接受这些特殊字符是不可取的,同样,用getchar()也不行。为此,你需要编写一个小函数,让它向DOS询问被按下的键的值。请看下例:
    #include
    int GetKey()
    {
       union REGS in,out;
       in.h.ah=0x8;
       int86(0x21,&in,&out);
       return out.h.al;
    }
    这种方法跳过C的输入/输出库,直接从键缓冲区中读取下一个键。这样做的好处是不会漏掉特殊的键码,并且所按的键能立即得到响应,而不用先存到缓冲区中,等到按下Enter键时才得到响应。
    通过这个函数你可以得到所按的键的整数值键码。请看下面这个测试程序:   
# include
# include
int GetKey()
      union REGS in, out ~
      in. h. ah = 0xS~
      int86( 0x21, &in, &out );
      return out. h. al;
int main()
{
      int c ;
      while (   c=GetKey() ) !=27 )
                     / * Loop until escape is pressed */
     {
          printf (\"Key = %d.\\n\" , c );
     }
       return 0 ;
}
如果你键入一个字符串,那么上述程序可能会输出这样的结果:
    key = 66.
    key=111.
    key=98.
    key=32.
    key=68.
    key=111.
    key=98.
    key=98.
    key=115.
    当你按下功能键或箭头键时,将发生不同的情况:你所看到的将是一个0,其后跟着一个字符值。这就是特殊键的表示方法:在一个0值后面跟着一个特殊的值。
    因此,你可以用两种方法来处理特殊键。首先,你可以检测GetKey()的返回值,一旦检测到一个0,你就按特殊的方式去处理GetKey()读入的下一个字符。其次,你可以在GetKey()中检测读入的字符值,一旦检测到一个0,就接着读入下一个字符值,然后按某种方式修改这个值,并返回修改后的值。第二种方法比第一种方法更好。下面是一个改进了的GetKey()函数:
/*  
New improved key-getting function.
*/
int GetKey()
      union REGS in, out;
      in. h. ah = 0x8;
      int86( 0x21, &in, &out );
      if (out. h. al == 0 )
            return GetKey ( ) + 128 ;
      else
            return out. h. al ;
  这种方法中更清晰也最有效,它使程序无需检查是否读入了特殊键,因此减轻了程序员的工作量。在这种方法中,特殊键的值总是大于128。  17.9  怎样防止用户向一个内存区域中输人过多的字符?

  有两个原因要防止用户向一个内存区域中输入过多的字符:第一,你可能只希望处理固定数目的字符;第二,可能也是更重要的原因,如果用户输入的字符数超过了缓冲区的容量,就会溢出缓冲区并且破坏相邻的内存数据。这种潜在的危险在C指南书籍中常常会被忽略。例如,对于下面的这段代码,如果用户输入的字符超过了50个,那将是很危险的:
  char bufE50];
  scanf(\"%s\",buf);
  解决这个问题的办法是在scanf()中指定要读入的字符串的最大长度,即在“%”和“s”之间插入一个数字,例如:
    \"%50s\"
这样,scanf()将最多从用户那里接受50个字符,任何多余的字符都将保留在输入缓冲区中,并且可以被其它的scanf()所获取。
    还有一点需要注意,即字符串需要有一个NUL终止符。因此,如果要从用户那里接受50个字符,那么字符串的长度必须是51,即50个字符供真正的字符串数据使用,还有一个字节供NUL终止符使用。
    下面的程序测试了这种方法:
# include
/*
      Program to show how to stop the
      user from typing too many characters in
      a field.
*/
int main()
{
      char str[50]; /* This is larger than you really need * /
    /*
       Now, accept only TEN characters from the user. You can test
          this by typing more than ten characters here and seeing
          what is printed.
     */
      scanf( \"%10s\", str);
     /*
          Print the string, verifying that it is, at most, ten characters.
     */
       printf( \"The output is : %s. \\n\", str) ;
       return (0) ;
    下面是这个程序的一个运行例子。当输入
  supercalifragilisticexpialidocious
后,程序将输出
    supercalif.

    17.10  怎样用0补齐一个数字?
    要想用0补齐一个数字,可以在格式说明符中的“%”后面插入一个以0开始的数字。可以用具体的例子来清楚地解释这一点:
    /*Print a five-character integer,padded with zeros.*/
    printf(\"%05d\",i);
/ * Print a floating point, padded left of the zero out to
    seven characters.  * /
printf(\"% 07f\", f);
如果你没有在数字前面加上O这个前缀,那么数字将被用空格而不是O来补齐。
下面的程序演示了这种技巧:
#include
int main ()
{
     int i = 123;
     printf( \"%d\\n\", i ) ;
     printf( \" %05d\\n\", i );
     printf( \"%07d\\n\", i );
     return( 0 );
}
  它的输出结果为:
  123
  00123
  OO00123

  17.11  怎样才能打印出美元一美分值?
    C语言并没有提供打印美元一美分值的现成功能。然而,这并不是一个难题,编写一个输出货币值的函数是非常容易的。当你编写了一个这样的函数后,你就可以在任何需要它的程序中调用它了。
    这样的函数往往既短小又简单,下文中将简单地描述它的工作方式。在本书中,这个函数被分解成几个较小的容易编写的程序段,因而很容易理解。把一个程序分解为较小的程序段的原因在第11章中已经介绍过了。
  这个函数需要使用一些标准C函数,因此你需要包含一些相应的头文件。在任何使用这个函数的程序的开头,你都应该确保包含以下这些头文件:
#tinclude
#include
# include
# include
    包含了所需的头文件后,你就可以创建一个接受美元值并按带逗号的形式打印该值的函数:   
void PrintDollars( double Dollars )
{
    char buf[20];
    int  l , a;
   sprintf( buf, \" %olf\", Dollars );
   l = strchr( buf, \'.\') - buf;
   for(a= (Dollars<0.0); a<1; ++a)
        printf( \"%c\", buf[a]) ;
        if(( ( ( l - a ) % 3) == 1 )&&(a !=l - 1 ) )
            printf( \" ,\" ) ;
  }
    你可能习惯于用浮点数来表示实数,这是很平常的。然而,浮点数通常不适合于金融工作,因为它有较大程度的不准确性,例如舍入错误。双精度类型的准确度比浮点数高得多,因此它更适合于真正的数字工作。
    只要写一个把整数值传递给该函数的程序,你就可以很方便地测试该函数。然而,该函数还不能打印小数或“零头”,为此,还要专门编写一个完成这项工作的函数:
void PrintCents ( double Cents)
{
   char buf[10 ];
   sprintf( buf, \"%-. 02f\" , Cents );
   printf(\"%s\\n\" , bur + 1 + (Cents <= 0) );

上述函数接受一个小数值并按正确的格式打印该值。同样,只要编写一个把小数值传递给该函数的程序,你就可以测试该函数了。
    现在你已经有了两个函数:一个能打印货币值的美元部分(整数部分),另一个能打印货币值的美分部分(小数部分)。你当然不希望先把一个数分解成两部分,然后再分别调用这两个函数!但是,你可以编写一个函数,用它来接受货币值并把该值分解成美元和美分两部分,然后再调用已有的另外两个函数。这个函数如下所示:
void DollarsAndCents ( double Amount )
{
     double Dollars = Amount >= 0.0 ? floor( Amount ) : ceil(Amount) ;
     double Cents = Amount - (double) Dollars;
     if ( Dollars < 0.0 ) printf( \"-\"  );
     printf( \" $\" );
     PrintDollars ( Dollars ) ;
     PrintCents ( Cents ) ;
}
    这就是所要的函数! DollarsAndCents()函数接受一个实数值(double类型),并按美元一美分的格式把该值打印到屏幕上。如果你要测试一下这个函数,你可以编写一个main()函数,让它去打印多个美元一美分值。下面就是这样的一个函数:
  int main()
  {
    double num= . 0123456789;
    int a ;
    for(a = 0; a<12; ++a )
    {
        DollarsAndCents( num );
        num *= 10.0;
    }  
    return( 0 );
}
  它的输出结果为:
    $O.01
    $O.12
    $1.23
    $12.35
    $123.46
    $1,234.57
    $12,345.68
    $123,456.79
    $1,234,567.89
    $12,345,678.90
    $123,456,789.OO
    $1,234,567,890.OO
    如果你想按其它的形式打印货币值,你可以很方便地修改这个程序,使其按其它格式打印数字。  17.12怎样按科学记数法打印数字?
    为了按科学记数法打印数字,必须在printf()函数中使用\"%e\"格式说明符,例如:
    float f=123456.78;
    printf(\"%e is in scientific\\n\",(float)i);
    当然,如果要对整数进行这样的处理,则必须先把它转换为浮点数:
    int i=10000;
    printf(\"%e is in scientific\\n\",f);
    下面的程序演示了格式说明符“%e”的作用:
#inclued
main ( )
{
    double f = 1.0 / 1000000. O;
    int i ;
    for(i = O; i< 14; ++ i )
  {
     printf( \" %f = %e\\n\" , f , f );
     f *= 10.0;
  }
  return( 0 );
}

   17.13  什么是ANSI驱动程序?
    每种计算机都有自己的处理屏幕的方法。这是非常必要的,如果完全局限于一种特定的标准,那么各项事业将会停滞不前。然而,当你试图为不同的计算机编写程序时,或者试图编写必须通过电话线进行通信的程序时,这种差别会带来很大的问题。为了解决这个问题,便产生了ANSI标准。
    ANSI标准试图为程序使用视频终端来完成某些标准任务而设定一个基本框架,例如以不同颜色打印文本,移动光标,清屏,等等。它通过定义一些特殊的字符序列来达到这个目的——当这些字符序列被送到屏幕上时,它们会以特殊的方式对屏幕起作用。
    然而,在有些计算机上,当你按正常的方式把这些字符序列送到屏幕上时,你所看到的将是这些字符本身,而不是它们要产生的效果。为了解决这个问题,你需要装入一个程序,通过它来检查送往屏幕的每一个字符,并删去其中的特殊字符(这样它们就不会被打印出来),然后实现这些特殊字符所指示的功能。
    在以MS-DOS为操作系统的计算机上,这个程序被称为ANSI.SYS。ANSI.SYS必须在计算机启动时被装入,为此你可以在CONFIG.SYS文件中加入下述语句:
    DRIVER=ANSI.SYS
    在实际情况中,ANSI.SYS驱动程序可能在别的目录下,这时你必须清楚地写出路径全名,例如:
    driver=c:\\sys\\dos\\ansi.sys

    17.14  怎样通过ANSI驱动程序来清屏?
    这种操作可以通过\"[2J”来完成,下面的程序演示了这一点:
# include
main ( )
{
      printf( \" %c[2JNice to have an empty screen. \\n\" , 27 ) ;
      return ( 0 );

    17.15  怎样通过ANSI驱动程序来存储光标位置?
    这种操作可以通过\"[s\"来完成,下面的程序演示了这一点:
    #include
    main()
    {
        printf( \"Cursor position is %c[s \\n\" , 27 );
        printf ( \"Interrupted ! \\n\" ) ;
        printf( \"%c[uSAVED! !\\n\" , 27 );

   return( 0 );
    }

    17.16  怎样通过ANSI驱动程序来恢复光标位置?
    这种操作可以通过“[u”来完成,请参见17.15中的例子。

    17.17  怎样通过ANSI驱动程序来改变屏幕颜色?
    完成这项任务的方法是先改变当前文本的背景色,然后清屏。下面的程序就是一个例子:
    # include
    int main ( )
    {
        printf( \" %0c[43;32m%c[2JOhh, pretty colors!\\n\", 27 , 27 ) ;
        return( 0 );
    }

    17.18  怎样通过ANSI驱动程序来写带有颜色的文本?
    文本的颜色是可以改变的文本属性之一。文本的属性可以通过“(esc>[m”来改变。在ANSI字符序列中,文本的属性是用数字来表示的。你可以用一条命令来设置多种属性,各种属性之间用分号分隔开,例如“[m\"。下面的程序演示了这一点:
    # include
    main ( )
    {
          printf(\"%c[32;44mPsychedelic, man.\\n\" , 27 );
        return( 0 );
    }
    以下列出了ANSI驱动程序所支持的属性,你的显示器可能不支持其中的某些选项:
        1—High Intensity(高强度)
        2一Low Intensity(低强度)
        3一Italic(斜体)
        4一Underline(下划线)
        5一Blinking(闪烁)
        6一Fast Blingking(快闪)
        7一Reverse(反转)
        8一Invisible(不可见)
    前景色:
        30一Black(黑)
        31一Red(红)
        32一Green(绿)
        33一Yellow(黄)
        34一Blue(蓝)
        35一Magenta(洋红)
        36一Cyan(青蓝)
        37一White(白)
    背景色:
        40—Black(黑)
        41一Red(红)
        42一Green(绿)
        43一Yellow(黄)
        44一Blue(蓝)
        45一Magenta(洋红)
        46一Cyan(青蓝)
        47一White(白)

    17.19怎样通过ANSI驱动程序来移动光标?
    移动光标有两种方式:相对移动和绝对移动。相对移动是指相对于当前光标位置的移动,例如“将光标上移两格”。绝对移动是指相对于屏幕左上角的移动,例如“将光标移到第10行第5列”。
    相对移动可按以下方式进行:
    “[#a”,其中#表示上移的格数
    “[#b”,其中#表示下移的格数
    “[#c”,其中#表示右移的格数
    “[#d”,其中#表示左移的格数
    将光标移到绝对位置的方法是:
    “[;H”,其中row和col分别表示目标位置所在的行数和列数。

阅读(331) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~