C语言编程常见问题解答之系统调用

PC中最主要的难题之一,也是最容易引起误解的,就是系统调用。系统调用所代表的那些函数实际上是计算机的所有底层操作——屏幕和磁盘的控制,键盘和鼠标的控制,文件系统的管理,时间,打印,这些只不过是系统调用所实现的一部分功能。
    总的来说,系统调用往往涉及到BIOS(基本输入输出系统)。实际中有好几种不同的BIOS,例如主板的BIOS负责初始硬件检测和系统引导,VGA BIOS(如果有VGA卡的话)处理所有的屏幕处理函数,固定磁盘BIOS管理硬盘驱动器,等等。DOS是位于这些低级BIOS之上的一个软件层,并且提供了进入这些低级BIOS的基本接口。一般说来,这意味着有一个DOS系统调用可以调用几乎所有你想使用的系统功能。实际上,DOS将调用相应的一种低级BIOS来完成所要求的任务。在本章中,你将会发现你既可以调用DOS来完成一项任务,也可以直接调用低级BIOS来完成相同的任务。

 

    14.1  怎样检索环境变量(environment variables)的值?
    ANSI C标准提供了一个名为getenv()的函数来完成这项任务。getenv()函数很简单一把指向要查找的环境串的指针传递给它,它就返回一个指向该变量值的指针。下面的程序说明了如何从C中获得环境变量PATH的值:
# include <slib. h>
main(int argc, char *  *  argv)
{
    char envValue[l29];             / *  buffer to store PATH * /
    char *  envPtr = envValue ;     / *  pointer to this buffer  * /
    envPtr = getenv("PATH");        /*  get the PATH */
    printf ("PATH= %s/n" , envPtr) ;   / * print the PATH * /
}

    如果你编译并运行了这个程序,你就会看到与在DOS提示符下输入PATH命令完全相同的结果。事实上,你可以用getenv()检索AUTOEXEC.BAT文件中的或者系统引导后在DOS揭示符下输入的所有环境变量的值。
    这里有一个小技巧。当运行Windows时,Windows设置了一个名为WINDIR的新的环境变量,它包含了Windows目录的路径全名。下面这段简单的程序用来检索这个串:
# include <slib. h>
main(int argc, char * *  argv)
{
    char envValue[l29];
    char *  envPtr = envValue ;
   envPtr = getenv("windir");
    / * print the Windows directory * /
    printf("The Windows Directory is  %s/n" ,  envPtr);
}
    这个程序还可以用来判断当前是否正在运行Windows,以及DOS程序是否运行在一个DOS shell下,而不是运行在“真正的"DOS下。注意,程序中的windir字符串是小写——这一点很重要,因为它对大小写是敏感的。如果你使用WINDIR,getenv()就会返回一个NULL串(表示变量未找到错误)。
    用一putenv()函数也可以设置环境变量。但要注意,该函数不是一个ANSI标准函数,在某些编译程序中它可能不以这个名字出现,或者根本就不存在。你可以用一putenv()函数做许多事情。实际上,在上面那个例子中,Windows正是用这个函数创建了windir环境变量。

    请参:
    14.2  怎样在程序中调用DOS函数?
    14.3  怎样在程序中调用BIOS函数?

    14.2  怎样在程序中调用DOS函数?
    其实,当调用printf(),fopen(),fclose(),名字以一dos开始的函数以及很多其它函数时,都将调用DOS函数。Microsoft和Borland还提供了一对名为int86()和int86x()的函数,使你不仅可以调用DOS函数,还可以调用其它低级函数。用这些函数可以跳过标准的C函数而直接调用DOS函数,这常常可以节省你的时间。下面的例子说明了如何通过调用DOS函数,而不是getch()和printf()函数,从键盘上得到一个字符并将其打印出来(该程序需要在大存储模式下编译)。
# include <slib. h>
# include <dos. h>
char GetAKey(void);
void OutputString(char  * );
main(int argc, char  * *  argv)
{
    char str[l28];
    union REGS regs;
    int ch;
    / * copy argument string; if none, use "Hello World"  * /
    strcpy(str,  (argv[1]== NULL ? "Hello World": argv[1])),

    while ((ch = GetAKey()) !  =27){
          OutputString(str);
    }
 }
char
GetAKeyO
{
     union  REGS regs;
     regs.h. ah = 1;     /*  function 1 is "get keyboard character"  * /
     int86(0x21, &regs, &regs);
     return( (char)regs. h. al) ;
}
void
OutputString(char  * string)
{
     union  REGS regs;
     struct SREGS segregs;
     / *  terminate  string for DOS function  * /
      * (string + strlen(string))  = '$';
     regs.h. ah = 9;     / *  function 9 is "print a string" * /
     regs.x. dx = FP_OFF(string) ;
     segregs. ds=  FP_SEG(string) ;
     int86x(0x21, &regs, &regs,  &segregs);
}
    上例创建了两个函数来代替getch()和printf(),它们是GetAKey()和OutputString()。实际上,函数GetAKey()与标准c函数getche()更为相似,因为它与getche()一样,都把键入的字符打印在屏幕上。这两个函数中分别通过int86()(在GetAKey()中)和int86x()(在OutputString()中)调用DOS函数来完成所要求的任务。
    可供函数int86()和int86x()调用的DOS函数实在太多了。尽管你会发现其中许多函数的功能已经被标准的C函数覆盖了,但你也会发现还有许多函数没有被覆盖。DOS也包含一些未公开的函数,它们既有趣又有用。DOS忙标志(DOS Busy Flag)就是一个很好的例子,它也被称作InDos标志。DOS函数34H返回指向一个系统内存位置的指针,该位置包含了DOS忙标志。当DOS正忙于做某些重要的事情并且不希望被调用(甚至不希望被它自己调用)时,该标志就被置为1;当DOS不忙时,该标志将被清除(被置为O)。该标志的作用是当DOS正在执行重要的代码时,把这一情况通知DOS。然而,该标志对程序员也是很有用的,因为他们能由此知道什么时候DOS处于忙状态。尽管从DOS 2.0版开始就有这个函数了,但因为Microsoft最近已经公开了这个函数,所以从技术角度上讲它已不再是一个未公开的函数。有几本很不错的书介绍了已公开和未公开的DOS函数,对这个问题有兴趣的读者可以去阅读这些书。

    请参见:
    14.3  怎样在程序中调用BIOS函数?

    14.3  怎样在程序中调用BIOS函数?
    与前文中的例子一样,在使用象一setvideomode()这样的函数时,将频繁地调用BIOS函数。此外,就连前文例子中使用过的DOS函数(INT 21H,AH=01H和INT 21H,AH=09H)最终也要通过调用BIOS来完成它们的任务。在这种情况下,DOS只是简单地把你的DOS请求传给相应的低级BIOS函数。下面的例子很好地说明了这一事实,该例与前文中的例子完成相同的任务,只不过它完全跳过了DOS,直接调用了BIOS。
# include <slib. h>
# include <dos. h>
char GetAKey(void) ;
void OutputString( char  * );
main(int argc, char  * *  argv)
{
    char str[128];
    union REGS regs;
    int ch;
    / * copy argument string; if none, use "Hello World"  * /
    strcpy(str,  (argv[1] == NULL ? "Hello World" :  argv[1]));

    while ((ch =  GetAKeyO) !=27){
        OutputString(str);
     }
 }
 
char
GetAKey()
{
    union REGS regs;
    regs. h. ah = 0;         /*  get character  */
    int86(0xl6, &xegs,  &regs);
    return( (char)regs. h. al) ;
 }
void
OutputString(char * string)
 {
     union REGS regs;
     regs. h. ah = 0x0E;      /*  print character  * /
     regs. h. bh = 0;
     /  * loop, printing all characters * /
     for(;  * string !='/0'; string+ + ){
        regs. h. al=  * string;
        int86(0xl0, &regs, &regs);
     }
 }
    你可以发现,唯一的变化发生在GetAKey()和OutputString()自身之中。函数GetAKey()跳过了DOS,直接调用键盘BIOS来获得字符(注意,在本例这个调用中,键入的字符并不在屏幕上显示,这一点与前文中的例子不同);函数OutputString()跳过了DOS,直接调用了Video BIOS来打印字符串。注意本例效率不高的一面——打印字符串的C代码必须位于一个循环中,每次只能打印一个字符。尽管Vidoeo BIOS支持打印字符串的函数,但C无法存取创建对该函数的调用所需的所有寄存器,因此不得不一次打印一个字符。不管怎样。运行该程序可以得到与前文例子相同的输出结果。

    请参见:
    14.2  怎样在程序中调用DOS函数?

    14.4  怎样在程序中存取重要的DOS内存位置?
    与DOS和BIOS函数一样,有很多内存位置也包含了计算机的一些有用和有趣的信息。你想不使用中断就知道当前显示模式吗?该信息存储在40:49H(段地址为40H,偏移量为49H)中。你想知道用户当前是否按下了Shift,Ctrl或Alt键吗?该信息存储在40:17H中。你想直接写屏吗?单色显示(Monochrome)模式的
视频缓冲区起始地址为B800:O,彩色文本模式和16色图形模式(低于640×480 16色)的视频缓冲区起始地址为B8000:0,其余标准图形模式(等于或高于640×480 16色)的视频缓冲区起始地址为A000:O,详见14.8。下面的例子说明了如何把彩色文本模式的字符打印到屏幕上,注意它只是对前文中的例子做了一点小小的修改。
# include <slib. h>
# include <dos. h>
char GetAKey(void) ;
void OutputString(int, int, unsigned int, char * );
main (int argc, char * *  argv)
{
     char str[l28];
     union REGS regs;
     int ch, tmp;
     / * copy argument string; if none, use "Hello World" * /
     strcpy(str,  (argv[1] == NULL  ? "Hello World"  : argv[1]));
     / * print the string in red at top of screen * /
     for(tmp = 0;((ch =  GetAKeyO)  ! = 27); tmp+=strlen(str)) {
        outputString(0,  tmp, 0x400,str);
     }
 }
char
GetAKey()
 {
     union REGS regs;
    regs. h. ah =  0;          / *  get character * /
    int86(0xl6, &regs, &regs);
    return((char)regs. h. al);
}
void
OutputString(int row, int col, unsigned int video Attribute,  char * outStr)
{
    unsigned short far *  videoPtr;
    videoPtr= (unsigned short far * )  (0xB800L <<16);
videoPtr + = (row *  80) + col;  /*  Move videoPtr to cursor position * /
videlAttribute & = 0xFF00;        / *  Ensure integrity of attribute  * /
    / *  print string to RAM * /
    while ( * outStr ! = '/0'){
       / *  If newline was  sent, move pointer to next line, column 0  * /
       if( (* outStr ==  '/n')  || (*outStr ==  'V') ){
          videoPtr + = (80- (((int)FP-OFF(videoPtr)/2) % 80));
          outStr+ + ;
          continue;
       }
      
       / *  If  backspace was requested, go back one  * /
       if( *outStr = = 8){
            videoPtr -- ;
            outStr++ ;
            continue;
       }
       /*  If BELL was requested, don't beep,  just print a  blank
           and go on  * /
       if ( * outStr = =  7) {
            videoPtr+ + ;
            outStr++ ;
            continue ;
       }
       / *  If TAB was requested, give it eight spaces  * /
       if ( * outStr ==  9){
             * videoPtr++  =  video Attribute  |  '   ' ;
             * videoPtr++  =  video Attribute  |  '   ' ;
             * videoPtr++  =  video Attribute  |  '   ' ;
             * videoPtr++  =  video Attribute  |  '   ' ;
             * videoPtr++  =  video Attribute  |  '   ' ;
             * videoPtr++  =  video Attribute  |  '   ' ;
             * videoPtr++  =  video Attribute  |  '   ' ;
             * videoPtr++  =  video Attribute  |  '   ' ;
           outStr+ + ;
           continue;
      }
      / * If  it was a regular character, print it  * /
       * videoPtr = videoAttribute  |  (unsigned char) * outStr;
      videoPtr+ + ;
      outStr + + ;
    } 
    return;
}
    显然,当你自己来完成把文本字符打印到屏幕上这项工作时,它是有些复杂的。笔者甚至已经对上例做了一些简化,即忽略了BELL字符和一些其它特殊字符的含义(但笔者还是实现了回车符和换行符)。不管怎样,这个程序所完成的任务与前文中的例子基本上是相同的,只不过现在打印时你要控制字符的颜色和位置。这个程序是从屏幕的顶端开始打印的。如果你想看更多的使用内存位置的例子,可以阅读20.12和20.17——其中的例子都使用了指向DOS内存的指针来查找关于计算机的一些有用信息。

    请参见:
    14.5  什么是BIOS?
    20.1  怎样获得命令行参数?
    20.12 怎样把数据从一个程序传给另一个程序?
    20.17 可以使热启动(Ctrl+Alt+Delete)失效吗?

    14.5  什么是BIOS?
    BIOS即基本输入输出系统,它是PC机的操作的基础。当计算机上电时,BIOS是第一个被执行的程序,DOS和其它程序都通过BIOS来存取计算机内部的各种硬件设备。
    然而,引导程序并不是计算机内唯一被称为BIOS的代码。实际上,PC机上电时要执行的BIOS通常被称为主板BIOS,因为它被存放在主板上。直到不久之前,这个BIOS还被固化在一块ROM芯片上,因而无法为了修改错误和扩充功能而重新编写它。现在,主板BIOS被存放在一块叫做Flash EPROM的可重新编程的存储器芯片中,但它还是原来的BIOS。不管怎样。主板BIOS会读遍系统内存,从而找到系统中其它一些硬件设备,这些设备都带有自身要使用的一些基础代码(即其它的BIOS代码)。例如,VGA卡就有其自身的BIOS,通常被称为Video BIOS或VGA BIOS;硬盘和软盘控制器也有一个BIOS,并且也在系统引导时被执行。当人们提及BIOS时,或者是指这些程序的集合,或者是指其中单独的一个BIOS,这两种说法部对。
    根据上述介绍,你应该知道BIOS并不是DOS——BIOS是PC机中最底层的功能软件。DOS刚好位于BIOS上面的一层,并且经常调用BIOS来完成一些基本操作,而这些操作可能会被你误认为是"DOS"函数。例如,你可能会用DOS函数40H来把数据写到硬盘上的一个文件中,而DOS最终还是要通过调用硬盘BIOS的函数03来把数据写到硬盘上。
    请参见:
    14.6  什么是中断?

    14.6  什么是中断?
    首先,中断分硬件中断和软件中断两种。中断为计算机的硬件设备和软件"部件"提供了一种相互交流的途径,这就是它的作用。那么,都有哪些中断呢?它们又是怎样实现这种交流的呢?
    PC机中的CPU通常都是Intel 80x86处理器,它有几条引脚用来中断CPU的当前工作,并使它转去进行其它工作。每条中断引脚上都连接着一些硬件设备(例如定时器),其作用是为这条引脚提供一个特定的电压。当中断事件发生时,处理器会停止执行当前正在执行的软件,保存当前的操作状态·然后去“处理”中断。处理器中事先已经装有一张中断向量表,其中列出了每个中断号以及当某个特定中断发生时所应执行的程序。
    以系统定时器为例——作为要完成的许多任务中的一部分,PC机需要维持一天的计时工作,其具体工作过程为:(1)一个硬件计时器每秒钟向CPU发出18次中断;(2)CPU停止当前的工作并在中断向量表中查找负责维持系统计时器数据的程序(这种程序叫做中断处理程序(interrupt handler),因为它的工作就是在中断发生时处理中断);(3)CPU执行该程序(将新的定时器数据存入系统内存),然后返回到刚才被中断的地方继续往下执行。当你的程序要求使用当前时间时,定时器数据就会按照你要求的格式被组织好并传给程序。以上的解释大大简化了定时器中断的工作情况,但它是一个很好的硬件中断的例子。
    系统定时器只是通过中断机制发生的数百个事件(有时被称为中断)中的一个。在很多时候,硬仵并不参与到中断处理过程中去。换句话说,软件经常会通过中断来调用其它软件,并且可以不需要硬件的参与。DOS和BIOS就是这方面的两个主要例子。当一个程序打开一个文件,读/写一个文件,把字符写到屏幕上,从键盘那里得到一个字符,甚至询问当前时间时,都需要有一个软件中断来完成这项任务。你可能不知道发生了这些事情,因为这些中断都深藏在你所调用的那些无足轻重的小函数(例如getch(),fopen()和ctime())的后面。
    在C中,你可以通过int86()和int86x()函数产生中断。int86()和int86x()函数要求用你想产生的中断号作为它们的一个参数。当你调用其中的一个函数时,CPU将象前面所讲的那样被中断,并俭查中断向量表,以找到需要执行的那个程序。在调用这两个函数时,通常将执行的是一个DOS或BIOS程序。表14.6列出了一些常见的中断,你可以通过它们设置或检索计算机的有关信息。注意这并不是一张完整的表,并且其中的每个中断都可以服务于数百种不同的函数。
                     表14.6  常见的PC中断
—————————————————————————————————————
  中断(hex)          描述
————一————————————————————————————————
  5                 屏幕打印服务
  10               
视频显示服务(MDA,CGA,EGA,VGA)
  11                获得设备清单
  12                获得内存大小
  13                磁盘服务
  14                串行口服务
  15                杂项功能服务
  16                键盘服务
  17                打印机服务
  1A                时钟服务
  21                DOS函数
  2F                DOS多路共享服务
  33                鼠标器服务
  67                EMS服务
--------------------------------------------------------------------------

    当你知道了什么是中断后,你就会认识到:当计算机处于空闲状态时,它每秒可能要处理几十个中断;而当计算机紧张工作时,它每秒经常要处理数百个中断。在20.12中有一个例子程序,你可以参照该程序写出自己的中断处理程序,从而使两个程序通过中断进行交流。如果你觉得有意思,不妨试一下。

    请参见:
    20.12  怎样把数据从一程序传给另一个程序? 

    14.7  使用ANSI函数和使用BIOS函数,哪种方式更好?
    两种方式各有利弊。你必须先回答几个问题,然后才能确定哪种方式适合你需要创建的那种应用。例如:你需要很快地实现你的应用吗?你的应用仅仅是用来“证实有关概念”,还是一个“真正的应用”呢?速度对你的应用重要吗?下面比较了使用ANSI函数和使用BIOS函数的基本优点:

    使用ANSI函数的优点:
      只需要printf()语句就可完成任务
      改变文本的颜色和属性很方便
      不管系统如何配置,都可以在所有PC机上工作
      无需记忆BIOS iN数

    使用BIOS函数的优点:
      运行速度快
      用BIOS可以做更多的事
      不需要设备驱动程序(使用ANSI iN数需要ANSI.SYS)
      无需记忆ANSI命令
  
     刚开始时,你会发现用ANSI函数编程是很不错的,并且能使你写出一些漂亮的程序。然而,不久你就可能会发现ANSI函数“有些碍事”,此时你就会想用BIOS函数。当然,以后你又发现BIOS函数有时也会“碍事”,此时你就想使用一种更快的方式。例如,14.4中的一个例子甚至不通过BIOS来把文本打印到屏幕上,你也许会发现这种方法比使用ANSI或BIOS函数更有趣。

    请参见:
    14.4  怎样在程序中存取重要的DoS内存位置?

    14.8  可以通过BIOS把显示模式改为VGA图形模式吗?
    当然可以。中断10H,即Video BIOS,负责处理文本模式和图形模式之间的转换。当你所运行的程序要进行文本模式和图形模式之间的相互转换时(即使该程序是Microsoft Windows),就需要通过Video BIOS来实现这种转换。每一种不同的设置都被称作一种显示模式。
    要改变显示模式,你必须通过int 10H服务来调用Video BIOS。这就是说,你必须向中断10H的中断处理程序发出中断请求。除中断号不同之外,这与实现DOS调用(int 21H)没有什么区别。下面的一段程序通过调用Video BIOS函数0,先从标准文本模式(模式3)切换到一个由命令行输入的模式号,然后再切换回来:
# include <slib. h>
# include <dos. h>
main(int argc, char  *  * argv)
{
    union REGS regs;
    int mode;
    / * accept Mode number in hex * /
    sscanf (argv[1] , " %x" , &mode) ;
    regs. h. ah =  0;            /*  AH = 0 means "change display mode"  */
    regs.h.al = (char)mode;      /*  AL = ??, where ?? is the Mode number *
    regs. x. bx =  0;            /*  Page number, usually zero  */
    int86(0xl0, &regs, &regs);   /*  Call the BIOS (intlO) * /
    printf("Mode 0x%X now active/n" ,  mode);
    printf ("Press any key to return. . . ") ;
    getch();
    regs. h. al = 3;              / *  return to Mode 3  * /
    int86(0xl0, &regs, &regs);
}
    有一个有趣的特点并没有在这个程序中表现出来,即该程序可以在不清屏的情况下改变显示模式。在某些场合,这一特点极为有用。要想改变显示模式,而又不影响屏幕内容,只需把存放在AI.寄存器中的显示模式值和80H或一下。例如,如果你要切换到模式13H,你只需把93H存入AL中,而程序中其余的代码可以保持不变。
    今天,在VESA Video BIOS标准中已经加入了VGA卡对扩充显示模式(见下文中的补充说明)的支持。然而,需要有一个新的“改变显示模式”函数来支持这些扩充模式。按照VESA标准,在切换VESA模式时,应该使用函数4FH,而不是前文例子中的函数O。下面的程序改进了前文中的例子,以切换VESA模式:
# include <slib. h>
#include <dos. h>
main(int argc, char  * *  argv)
{
    union REGS regs;
    int mode;
    / *  accept Mode number in hex * /
    sscanf (argv[1], " %x" , &mode);
    regs. x. ax = 0x4F02;       /* change display mode  * /
    regs. x. bx = (short )mode;   / * three-digit mode number  * /
    int86(0x10, &regs, &regs);   /*  Call the BIOS (intlO)  * /
    if(regs.h.al !=0x4F){
        printf("VESA modes NOT  supported! /n" );
    }
    else {
        printf("Mode  Ox%X now active/n" , mode);
        printf ("Press any key to return. . . " ) ;
        getch() ;
    }
    regs. h. al = 3;         / *  return to Mode 3  * /
    int86(0x10,&regs, &regs) ;
}

    注意,在切换VESA模式时,不能通过把模式号和80H或一下来达到不清屏的目的。但是。只要把原来两位的(十六进制)模式号的最高位往前移一位,就得到了VESA模式号(所有VESA模式号的长度都是三位(十六进制),见下文中的补充说明)。因此,为了切换到VESA模式101H并且保留屏幕上的内容,你只需把VESA模式号换为901H。

    关于显示模式的补充说明:
    IBM推出了一种显示模式标准,该标准试图定义所有可能会用到的显示模式,其中包括所有可能的像素层次(颜色的数目)。因此,IBM创建了19种显示模式(从OH到13H)。表14.8a给出了这种显示模式标准。

                   14.8a 标准显示模式
-------------------------------------------------------------------------------
    模式(H)            分辨率            图形/文本           颜色
-------------------------------------------------------------------------------
    0                  40X 25             文本                 单色
    1                  40 X 25            文本                 16
    2                  80X 25             文本                 单色
    3                  80X 25             文本                 16
    4                  320X 200           图形                 4
    5                  320X 200           图形                 4级灰度
    6                  640X 200           图形                 单色
    7                  80 X 25            文本                 单色
    8                  160X 200           图形                 16
    9                  320X 200           图形                 16
    A                  640 x 200          图形                 4
    B                  保留给EC-A BIOS使用
   C                   保留给EGA BIOS使用
   D                   320×200           图形                 16
   E                   640×200           图形                 16
   F                   640×350           图形                 单色
   10                  640×350           图形                  4
   11                  640×480           图形                 单色
   12                  640×480           图形                16
   13                  320×200           图形                 256
-------------------------------------------------------------------------------
    那么,你见过其中的某些模式吗?模式3是80×25彩色文本模式,也就是PC机上电时你所看到的模式。当你把"VGA"(随Windows提供的一个驱动程序)选为Microsoft Windows3.x的驱动程序时,你所看到的就是模式12(H)。注意,上表中并没有一种颜色多于256色或分辨率高于640×480的模式。多年以来,模式4,9和D一直是DOS游戏开发者喜欢用的模式,它们拥有“高”达320×200的分辨率和足够的颜色(4或16种),足以显示一些“象样”的图形。所有流行
的动画游戏几乎都使用模式13,例如DOOM(一代和二代),id软件公司的new Heretic,Apogee公司的Rise of the Triad,Interplay公司的Descent,等等。实际上,许多动画游戏在VGA卡上耍了个小花招,即把模式13的分辨率改为320×240    这种模式被称为模式x,它有更多的内存页。可以提高图形质量和显示速度。
    那么,其它一些常见的显示模式又是从哪里来的呢?它们是由VGA卡的制造商提供的。这些你可能已经熟悉的显示模式来自各种各样的渠道,但不管它们来自何处,VGA卡的制造商们都把它们加到了自己的VGA卡中,以增加这些VGA卡的价值。这些模式通常被称为扩充显示模式(extended display mode)。由于竞争和资本积累的原因,VGA卡的制造商们逐步转向了这些更高级的显示模式。有人还试过其它一些显示模式(听说过1152×900吗?),但并不象上述模式那样受欢迎。
    那么。什么是VESA呢?它与VGA卡有什么关系呢?尽管VGA卡的制造商们都选择了支持同样的一组显示模式(包括扩充模式),但他们都按自己的专用方式去实现其中的扩充模式,而游戏厂商和其它软件厂商不得不去支持市场上每一种VGA卡的每一种专用方式。因此,一些制造商和其它方面的一些代表一起组成了一个委员会,以尽可能地使这些卡的设置和
编程标准化,这个委员会就是VESA(Video Electronic Standards Association)。VESA委员会采用了一种扩充显示模式的标准,从而使软件可以通过普通的BIOS调用来设置和初始化所有符合该标准的VGA卡。基本上可以这样说,在美国出售的所有的VGA卡都支持某种VESA标准。
    所有的VESA模式(即VESA标准所包含的那些显示模式)都采用宽度为9位(bit)的模式号,而不是标准模式的8位(hit)模式号。使用了9位(bit)的模式号后,就可以用三位十六进制数来表示VESA模式了,而IBM标准模式只能用两位十六进制数(在表14.8a中,从0到13H)来表示,这样就避免了模式号的冲突。因此,所有的VESA模式号都大于100H。VESA模式是这样起作用的:假设你想让你的VGA卡以1024×768和256色这样的模式显示,而这种模式就是VESA模式105,因此你要用模式号105作一次BIOS调用。Video BIOS(有时叫做VESA BIOS)会把VESA模式号翻译成内部专用号,以完成实际的模式切换工作。VGA卡的制造商们在每一块VGA卡上都提供了一种可以完成上述翻译工作的Video BIOS,因此你只需要搞清楚VESA模式号就行了。表14.8b列出了最新的VESA显示模式(VESA是一个不断发展的标准。)
                         表14.8b  VESA显示模式
----------------------------------------------------------------------------
  分辨率                              颜色                       VESA模式
----------------------------------------------------------------------------
  640X400                             256                        100
  640X480                             256                        101
  640X480                             32768                      110
  640X480                             65536                      111
  640X480                             16. 7M                     112
  800X600                             16                         102
  800X600                             256                        103
  800X600                             32768                      113
  800X600                             65536                      114
  800X600                             16. 7M                     115
  1024X768                            16                         104
  1024X768                            256                        105
  1024X768                            32768                      116
  1024X768                            65536                      117
  1024X768                            16. 7M                     118
  1280X1024                           16                         106
  1280X1024                           256                        107
  1280X1024                           32768                      119
  1280X1024                           65536                      11A
  1280X1024                           16. 7M                     11B
-----------------------------------------------------------------------------
    注意,这些都是人们熟悉的显示模式,特别是在使用Microsoft Windows时,这些模式更为常见。

    请参见:
    14.6什么是中断?

    14.9运算符的优先级总能起作用吗(从左至右,从右至左)?
    如果你是指“一个运算符的结合性会从自右至左变为自左至右吗?反过来会吗?”,那么答案是否定的。如果你是指“一个优先级较低的运算符会先于一个优先级较高的运算符被执行吗?”,那么答案是肯定的。表14.9按优先级从高到低的顺序列出了所有的运算符及其结合性:

                 表14.9运算符优先级
----------------------------------------------------------------
  运算符                                 结合性   
----------------------------------------------------------------
  () [] ->                               自左至右
  ! ~ ++ -- -(类型转换) * &              自右至左
  sizeof  * / %                          自左至右
  + -                                    自左至右
  <<  >>                                 自左至右
  << =  >>=                              自左至右
  ==  !=                                 自左至右
  &                                      自左至右
  ^                                      自左至右
  |                                      自左至右
  &&                                     自左至右
  ||                                     自左至右
  ?:                                     自右至左
  =  +=  -=                              自右至左
  ,                                      自左至右
------------------------------------------------------------------
     注意,运算符“!=”的优先级高于“=”(实际上,几乎所有的运算符的优先级都高于“=”)。下面两行语句说明了运算符优先级的差异是怎样给程序员带来麻烦的:
    while(ch=getch()!=27)printf(”Got a character/n”);
    while((ch=geteh())!=27)printf("Got a character/n");    ’
    显然,上述语句的目的是从键盘上接收一个字符,并与十进制值27(Escape键)进行比较。不幸的是,在第一条语句中,getch()与Escape键进行了比较,其比较结果(TRUE或FALSE)而不是从键盘上输入的字符被赋给了ch。这是因为运算符“!=”的优先级高于“=”。
    在第二条语句中,表达式"ch=geteh()”的外边加上了括号。因为括号的优先级最高,所以来自键盘的字符先被赋给ch,然后再与Escape键进行比较,并把比较结果(TRUE或FALSE)返回给while语句,这才是程序真正的目的(当while的条件为TRUE时,打印相应的句子)。需要进一步提出的是,与27比较的并不是ch,而是表达式"ch—getch()”的结果。在这个例子中,这一点可能不会造成什么影响,但括号确实可以改变代码的组织方式和运行方式。当一个语句中有多个用括号括起来的表达式时,代码的执行顺序是从最里层的括号到最外层,同层的括号则从左到右执行。
    注意,每个运算符在单独情况下的结合性(自左至右,或自右至左)都是不会改变的,但优先级的顺序可以改变。

  14.10  函数参数的类型必须在函数头部或紧跟在其后说明口马?为什么?
  ANSI标准要求函数参数的类型要在函数头部说明。在第20章中你将会发现,C语言最初设计于70年代,并且运行于UNX操作系统上,显然当时还没有什么ANSI C标准,因此早期的C编译程序要求在紧接着函数头部的部分说明参数的类型。
    现在,ANSI标准要求参数的类型应该在函数头部说明。以前的方法中存在的问题是不允许进行参数检查——编译程序只能进行函数返回值检查。如果不检查参数,就无法判断程序员传递给函数的参数类型是否正确。通过要求在函数头部说明参数,以及要求说明函数原型(包括参数类型),编译程序就能检查传递给函数的参数是否正确。

    请参见:
    14.11 程序应该总是包含main()的一个原型吗?

    14.11 程序应该总是包含main()的一个原型吗?
    当然,为什么不呢?虽然这不是必需的,但这是一种好的编程风格。人们都知道main()的参数类型,而你的程序可以定义其返回值的类型。你可能注意到了本章(其它章中可能也有)的例子中并没有说明main()的原型,并且在main()的函数体中也没有明确地表示返回值的类型,甚至连renturn语句也没有。笔者在按这种方式写这些例子时,隐含使用了一个返回整型值的void函数,但是,由于没有return语句,程序可能会返回一个无用值。这种方式不是一种好的编程风格,好的编程风格应该描述包括main()在内的所有函数的原型以及相应的返回值。

    请参见:
    14.12 main()应该总是返回一个值吗?

    14.12 main()应该总是返回一个值吗?
    main()不必总是带有返回值,因为它的调用者,通常是COMMAND.CoM,并不怎么关心返回值。偶而,你的程序可能会用在一个批处理文件中,而这个文件会到DOS的errorLevel符号中检查一个返回码。因此,main()是否有返回值完全取决于你自己,但是,为了以防万一,给main()的调用者返回一个值总是好的。
    如 main()返回void类型(或者没有return语句),也不会引起任何问题。

    请参见:
    14.11程序应该总是包含main()的一个原型吗?

    14.13  可以通过BIOS控制鼠标吗?
    可以。你可以通过中断33H调用鼠标服务程序。表14.13列出了中断33H中最常用的鼠标服务程序。
   
                           表14.13鼠标中断服务
--------------------------------------------------------------------------
    功能号                                描  述
--------------------------------------------------------------------------
    0                         初始化鼠标;当前可见则隐藏它
    1                         显示鼠标
    2                         隐藏鼠标
    3                         获得鼠标位置
    4                         设置鼠标位置
    6                         检查鼠标按钮是否被按下
    7                         设置鼠标的水平限制值
    8                         设置鼠标的垂直限制值
    9                         设置图形模式鼠标形状
    10                        设置文本模式鼠标风格
    11                        获得鼠标的移动步值
---------------------------------------------------------------------------

    下面的例子通过上表中的一些鼠标服务程序来控制一个文本模式的鼠标:
# include <slib. h>
# include <dos. h>
main()
{
    union REGS regs;
    printf("Initializing Mouse. . . ") ;
    regs. x. ax =  0;
    int86(0x33, &regs,  &regs);
    printf("/nShowing Mouse. . . ") ;
    regs. x.ax =  1;
    int86(0x33, &regs,  &regs);
    printf ("/nMove mouse around. Press any key to quit. . . ") ;
    getch() ;
    printf ("/nHiding Mouse. . . " ) ;
    regs. x. ax =  2;
    int86(0x33, &regs,  &regs);
    printf("/nDone/n");
 }

    当运行这个程序时,屏幕上会出现一个闪烁的可以移动的块状光标。无论什么时候,你都可以通过函数3向鼠标处理程序询问鼠标的位置。实际上,笔者用表14.13中的函数编写了一整套鼠标库函数,并且在笔者的许多使用文本模式鼠标的程序中使用了这套函数。
    为了使用上表中的函数,你必须安装一种鼠标驱动程序。通常可以通过AUTOEXEC.BAT文件来安装鼠标驱动程序。然而,现在运行Windows时通常只安装一种Windows鼠标驱动程序,在这种情况下,你必须先运行在DOS shell下,然后才能调用这些鼠标函数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C 语言编程常见问题解答 【作者】[美]Paul S.R. Chisholm 译:张芳妮 吕 波 【出版社】清华大学出版社 C语言编程常见问题解答(目录) 第l章 C语言 1. 1 什么是局部程序块(local block)? 1. 2 可以把变量保存在局部程序块中吗? 1. 3 什么时候用一条switch语句比用多条if语句更好? 1. 4 switch语句必须包含default分支吗? 1. 5 switch语句的最后—个分支可以不要break语句吗? 1. 6 除了在for语句中之外,在哪些情况下还要使用逗号运算? 1. 7 怎样才能知道循环是否提前结束了? 1. 8 goto,longjmp()和setjmp()之间有什么区别? 1. 9 什么是左值(lvaule)? 1. 10 数组(array)可以是左值吗? 1. 11 什么是右值(rvaule)? 1. 12 运算符的优先级总能保证是“自左至右”或“自右至左”的顺序吗? 1. 13 ++var和var++有什么区别? 1. 14 取模运算符(modulusoperator)“%”的作用是什么? 第2章 变量和数据存储 2. 1 变量存储在内存(memory)中的什么地方? 2. 2 变量必须初始化吗? 2. 3 什么是页抖动(pagethrashing)? 2. 4 什么是const指针? 2. 5 什么时候应该使用register修饰符?它真的有用吗? 2. 6 什么时候应该使用volatile修饰符? 2. 7 一个变量可以同时被说明为const和volatile吗? 2. 8 什么时候应该使用const修饰符? 2. 9 浮点数比较(floating—point comparisons)的可靠性如何? 2. 10 怎样判断一个数字型变量可以容纳的最大值? 2. 11 对不同类型的变量进行算术运算会有问题吗? 2. 12 什么是运算符升级(operator promotion)? 2. 13 什么时候应该使用类型强制转换(typecast)? 2. 14 什么时候不应该使用类型强制转换(typecast)? 2. 15 可以在头文件中说明或定义变量吗? 2. 16 说明一个变量和定义一个变量有什么区别? 2. 17 可以在头文件中说明static变量吗? 2.18 用const说明常量有什么好处? 第3章 排序与查找 排 序 查 找 排序或查找的性能 3.1 哪一种排序方法最方便? 3.2 哪一种排序方法最快? 3.3 当要排序的数据集因太大而无法全部装入内存时,应怎样排序? 3.4 哪一种查找方法最方便? 3.5 哪一种查找方法最快? 3.6 什么是哈希查找? 3.7 怎样对链表进行排序? 3.8 怎样查找链表中的数据? 第4章 数据文件 4.1 当errno为一个非零值时,是否有错误发生? 4.2 什么是流(stream)? 4.3 怎样重定向—个标准流? 4.4 怎样恢复一个重定向了的标准流? 4.5 stdout能被强制打印到非屏幕设备上吗? 4.6 文本模式(text mode)和二进制模式(binary mode)有什么区别? 4.7 怎样判断是使用流函数还是使用低级函数? 4.8 怎样列出某个目录下的文件? 4.9 怎样列出—个文件的日期和时间? 4.10 怎样对某个目录下的文件名进行排序? 4.1l 怎样判断一个文件的属性? 4.12 怎样查看PATH环境变量? 4.13 怎样打开一个同时能被其它程序修改的文件? 4.14 怎样确保只有你的程序能存取一个文件? 4.15 怎样防止其它程序修改你正在修改的那部分文件内容? 4.16 怎样—次打开20个以上的文件? 4.17 怎样避开"Abort,Retry,Fail"消息? 4.18 怎样读写以逗号分界的文本? 第5章 编译预处理 5.1 什么是宏(macro)?怎样使用宏? 5.2 预处理程序(preprocessor)有什么作用? 5.3 怎样避免多次包含同—个头文件? 5.4 可以用#include指令包含类型名不是“.h”的文件吗? 5.5 用#define指令说明常量有什么好处? 5.6 用enum关键字说明常量有什么好处? 5.7 与用#define指令说明常量相比,用enum关键字说明常量有什么好处? 5.8 如何使部分程序在演示版中失效? 5.9 什么时候应该用宏代替函数? 5.10 使用宏更好,还是使用函数更好? 5.11 在程序中加入注释的最好方法是什么? 5.12 #include<file>和#include“file”有什么不同? 5.13 你能指定在编译时包含哪一个头文件吗? 5.14 包含文件可以嵌套吗? 5.15 包含文件最多可以嵌套几层? 5.16 连接运算符“##”有什么作用? 5.17 怎样建立对类型敏感的宏? 5.18 什么是标准预定义宏? 5.19 怎样才能使程序打印出发生错误的行号? 5.20 怎样才能使程序打印出发生错误的源文件名? 5.2l 怎样判断一个程序是用C编译程序环是用C++编译程序编译的? 5.22 预处理指令#pragma有什么作用? 5.23 #line有什么作用? 5.24 标准预定义宏_FILE_有什么作用? 5.25 怎样在程序中打印源文件名? 5.26 标准预定义宏_LINE_有什么作用? 5.27 怎样在程序中打印源文件的当前行号? 5.28 标准预定义宏_DATE_和_TIME_有什么作用? 5.29 怎样在程序中打印编译日期和时间? 5.30 怎样判断一个程序是否遵循ANSIC标准? 5.31 怎样取消一个已定义的宏? 5.32 怎样检查一个符号是否已被定义? 5.33 C语言提供哪些常用的宏? 第6章 字符串操作 6.l 串拷贝(strcpy)和内存拷贝(memcpy)有什么不同?它们适合于在哪种情况下使用? 6.2 怎样删去字符串尾部的空格? 6.3 怎样删去字符串头部的空格? 6.4 怎样使字符串右对齐? 6.5 怎样将字符串打印成指定长度? 6.6 怎样拷贝字符串的一部分? 6.7 怎样将数字转换为字符串? 6.8 怎样将字符串转换为数字? 6.9 怎样打印字符串的一部分? 6.10 怎样判判断两个字符串是否相同? 第7章 指针和内存分配 7.1 什么是间接引用(indirection)? 7.2 最多可以使用几层指针? 7.3 什么是空指针? 7.4 什么时候使用空指针? 7.5 什么是void指针? 7.6 什么时候使用void指针? 7.7 两个指针可以相减吗?为什么? 7.8 把一个值加到一个指针上意味着什么? 7.9 NULL总是被定义为0吗? 7.10 NULL总是等于0吗? 7.11 用指针作if语句的条件表达式意味着什么? 7.12 两个指针可以相加吗?为什么? 7.13 怎样使用指向函数的指针? 7.14 怎样用指向函数的指针作函数的参数? 7.15 数组的大小可以在程序运行时定义吗? 7.16 用malloc()函数更好还是用calloc()函数更好? 7.17 怎样说明一个大于64KB的数组? 7.18 far和near之间有什么区别? 7.19 什么时候使用far指针? 7.20 什么是栈(stack)? 7.21 什么是堆(heap)? 7.22 两次释放一个指针会导致什么结果? 7.23 NULL和NUL有什么不同? 7.24 为什么不能给空指针赋值?什么是总线错误、内存错误和内存信息转储? 7.25 怎样确定一块已分配的内存的大小? 7.26 free()函数是怎样知道要释放的内存块的大小的? 7.27 可以对void指针进行算术运算吗? 7.28 怎样打印一个地址? 第8章 函数 8.1 什么时候说明函数? 8.2 为什么要说明函数原型? 8.3 一个函数可以有多少个参数? 8.4 什么是内部函数? 8.5 如果一个函数没有返回值,是否需要加入return语句? 8.6 怎样把数组作为参数传递给函数? 8.7 在程序退出main()函数之后,还有可能执行一部分代码吗? 8.8 用PASCAL修饰符说明的函数与普通C函数有什么不同? 8.9 exit()和return有什么不同? . 第9章 数组 9.1 数组的下标总是从0开始吗? 9.2 可以使用数组后面第—个元素的地址吗? 9.3 为什么要小心对待位于数组后面的那些元素的地址呢? 9.4 在把数组作为参数传递给函数时,可以通过sizeof运算符告诉函数数组的大小吗? 9.5 通过指针或带下标的数组名都可以访问数组中的元素,哪一种方式更好呢? 9.6 可以把另外一个地址赋给一个数组名吗? 9.7 array_name和&array;_name有什么不同? 9.8 为什么用const说明的常量不能用来定义一个数组的初始大小? 9.9 字符串和数组有什么不同? 第10章 位(bit)和字节(byte) 10.1 用什么方法存储标志(flag)效率最高? 10.2 什么是“位屏蔽(bit masking)”? 10.3 位域(bit fields)是可移植的吗? 10.4 移位和乘以2这两种方式中哪一种更好? 10.5 什么是高位字节(high-order byte)和低位字节(low-order byte)? 10.6 16位和32位的数是怎样存储的? 第11章 调试 11.1 如果我运行的程序挂起了,应该怎么办? 11.2 如何检测内存漏洞(leak)? 11.3 调试程序的最好方法是什么? 11.4 怎样调试TSR程序? 11.5 怎样获得一个能报告条件失败的程序? 第12章 标准库函数 12.1 为什么应该使用标准库函数而不要自己编写函数? 12.2 为了定义我要使用的标准库函数,我需要使用哪些头文件? 12.3 怎样编写参数数目可变的函数? 12.4 独立(free—standing)环境和宿主(hosted)环境之间有什么区别? 12.5 对字符串进行操作的标准库函数有哪些? 12.6 对内存进行操作的标准库函数有哪些? 12.7 怎样判断一个字符是数字、字母或其它类别的符号? 12.8 什么是“局部环境(locale)”? 12.9 有没有办法从一个或多个函数中跳出? 12.10 什么是信号(signal)?用信号能做什么? 12.11 为什么变量名不能以下划线开始? 12.12 为什么编译程序提供了两个版本的malloc()函数? 12.13 适用于整数和浮点数的数学函数分别有哪些? 12.14 什么是多字节字符(multibyte characters)? 12.15 怎样操作由多字节字符组成的字符串? 第13章 时间和日期 13.1 怎样把日期存储到单个数字中?有这方面的标准吗? 13.2 怎样把时间存储到单个数字中?有这方面的标准吗? 13.3 为什么定义了这么多不同的时间标准? 13.4 存储日期的最好方法是哪一种? 13.5 存储时间的最好方法是哪一种? 第14章 系统调用 14.1 怎样检查环境变量(environment variables)的值? 14.2 怎样在程序中调用DOS函数? 14.3 怎样在程序中调用BIOS函数? 14.4 怎样在程序中存取重要的DOS内存位置? 14.5 什么是BIOS? 14.6 什么是中断? 14.7 使用ANSI函数和使用BIOS函数,哪种方式更好? 14.8 可以通过BIOS把显示模式改为VGA图形模式吗? 14.9 运算符的优先级总能起作用吗(从左至右,从右至左)? 14.10 函数参数的类型必须在函数头部或紧跟在其后说明吗?为什么? 14.11 程序应该总是包含main()的一个原型吗? 14.12 main()应该总是返回一个值吗? 14.13 可以通过BIOS控制鼠标吗? 第15章 可移植性 15.1 编译程序中的C++扩充功能可以用在C程序中吗? 15.2 C++和C有什么区别? 15.3 在C程序中可以用“∥”作注释符吗? 15.4 char,short,int和long类型分别有多长? 15.5 高位优先(big-endian)与低位优先(little—endian)的计算机有什么区别? 第16章 ANSI/ISO标准 16.1 运算符的优先级总能起作用吗? 16.2 函数参数类型必须在函数参数表中或紧跟其后的部分中说明吗? 16.3 程序中必须包含main()的原型吗? 16.4 main()应该总是返回一个值吗? 第17章 用户界面——屏幕和键盘 17.1 为什么直到程序结束时才看到屏幕输出? 17.2 怎样在屏幕上定位光标? 17.3 向屏幕上写数据的最简单的方法是什么? 17.4 向屏幕上写文本的最快的方法是什么? 17.5 怎样防止用户用Ctr+Break键中止程序的运行? 17.6 怎样才能只得到一种特定类型的数据,例如字符型数据? 17.7 为什么有时不应该用scanf()来接收数据? 17.8 怎样在程序中使用功能键和箭头键? 17.9 怎样防止用户向一个内存区域中输入过多的字符? 17.10 怎样用0补齐一个数字? 17.11 怎样才能打印出美元一美分值? 17.12 怎样按科学记数法打印数字? 17.13 什么是ANSI驱动程序? 17.14 怎样通过ANSI驱动程序来清屏? 17.15 怎样通过ANSI驱动程序来存储光标位置? 17.16 怎样通过ANSI驱动程序来恢复光标位置? 17.17 怎样通过ANSI驱动程序来改变屏幕颜色? 17.18 怎样通过ANSI驱动程序来写带有颜色的文本? 17.19 怎样通过ANSI驱动程序来移动光标? 第18章 程序的编写和编译 18.1 程序是应该写成一个源文件还是多个源文件? 18.2 各种存储模式之间有什么区别? 18.3 最常使用的存储模式有哪些? 18.4 应该使用哪种存储模式? 18.5 怎样生成一个".COM"文件? 18.6 ".COM"文件有哪些地方优于".EXE"文件? 18.7 当一个库被连接到目标上时,库中的所有函数是否都会被加到一个".EXE"文件中? 18.8 可以把多个库函数包含在同一个源文件中吗? 18.9 为什么要建立一个库? 18.10 如果一个程序包含多个源文件,怎样使它们都能正常工作? 18.11 连接过程中出现"DGROUP:group exceeds 64K"消息是怎么回事? 18.12 怎样防止程序用尽内存? 18.13 如果程序太大而不能在DOS下运行,怎样才能使它在DOS下运行呢? 18.14 怎样才能使DOS程序获得超过640KB的可用内存呢? 18.15 近程型(near)和远程型(far)的区别是什么? 第19章编程风格和标准 19.1 可以在变量名中使用下划线吗? 19.2 可以用变量名来指示变量的数据类型吗? 19.3 使用注释会影响程序的速度、大小或效率吗? 19.4 使用空白符会影响程序的速度、大小或效率吗? 19.5 什么是骆驼式命名法? 19.6 较长的变量名会影响程序的速度、大小或效率吗? 19.7 给函数命名的正确方法是什么? 19.8 使用大括号的正确方法是什么? 19.9 一个变量名应该使用多少个字母?ANSI。标准允许有多少个有效字符? 19.10 什么是匈牙利式命名法?应该使用它吗? 19.11 什么是重复处理(iterative processing)? 19.12 什么是递归(recursion)?怎样使用递归? 19.13 在C语言中,表示真和假的最好方法是什么? 19.14 空循环(null loops)和无穷循环(infinite loops)有什么区别? 19.15 continue和break有什么区别? 第20章 杂项(Miscellaneous) 20.1 怎样获得命令行参数? 20.2 程序总是可以使用命令行参数吗? 20.3“异常处理(exception handling)”和“结构化异常处理(structured exception handling)”有什么区别? 20.4 怎样在DOS程序中建立一个延时器(delay timer)? 20.5 Kernighan和Ritchie是谁? 20.6 怎样产生随机数? 20.7 什么时候应该使用32位编译程序? 20.8 怎样中断一个Windows程序? 20.9 为什么要使用静态变量? 20.10 怎样在一个程序后面运行另一个程序? 20.11 怎样在一个程序执行期间运行另一个程序? 20.12 怎样把数据从一个程序传给另一个程序? 20.13 怎样判断正在运行的程序所在的目录? 20.14 怎样找到程序中的重要文件(数据库,配置文件,等等)? 20.15 本书的有些例子程序有许多缺陷,为什么不把它们写得更好? 20.16 怎样使用Ctr+Break失效? 20.17 可以使热启动(Ctrl+Alt+Delete)失效吗? 20.18 怎样判断一个字符是否是一个字母? 20.19 怎样判断一个字符是否是一个数字? 20.20 怎样把一个十六进制的值赋给一个变量? 20. 21 怎样把一个八进制的值赋给一个变量? 20.22 什么是二进制? 20.23 什么是八进制? 20.24 什么是十六进制? 20.25 什么是换码符(escape characters)? 附 录 常用函数的包含文件
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值