论坛交流
首页办公自动化| 网页制作| 平面设计| 动画制作| 数据库开发| 程序设计| 全部视频教程
应用视频: Windows | Word2007 | Excel2007 | PowerPoint2007 | Dreamweaver 8 | Fireworks 8 | Flash 8 | Photoshop cs | CorelDraw 12
编程视频: C语言视频教程 | HTML | Div+Css布局 | Javascript | Access数据库 | Asp | Sql Server数据库Asp.net  | Flash AS
当前位置 > 文字教程 > C语言程序设计教程
Tag:新手,函数,指针,数据类型,对象,Turbo,入门,运算符,数组,结构,二级,,tc,游戏,试题,问答,编译,视频教程

C语言编程常见问题解答之ANSI/ISO标准

文章类别:C语言程序设计 | 发表日期:2008-9-24 14:36:32

    假如你不理解C语言标准的价值,你就不会知道你是怎样地幸运。
    一个C程序员会期望一个C程序无论是在哪里开发的,在另一个编译程序中都能通过编译。实际上不能完全做到这一点,因为许多头文件和函数库都是针对某些特定的编译程序或平台的。有些(很少!)语言扩充性能,例如基于Intel的编译程序所使用的near和far要害字以及寄存器伪变量,也只不过是某种平台的开发商们所认可的一种标准。
    假如你认为靠一种标准走遍天下是理所当然的,就象左脚踩加速器,右脚踩刹车一样,那么你的视野未免有些狭窄。有两种不同的BASIC标准,但都没有得到广泛的支持;世界上最流行的Pascal编译程序并不符合正式的标准;现在正在发展的C++标准,由于变化太快,也没有得到广泛的支持;有些实现遵循一种严格的Ada标准,但Ada标准也没能大规模地占领世界市场。
    从技术上讲有两种C语言标准,一种来自ANSI(American National Standard Institute,美国国家标准协会)X3J11委员会,另一种来自ISO(International Standard Organization,国际标准协会)9899—1990。由于ISO标准中的某些改进优于ANSI标准,而ANSI标准也接受了这个国际版本,因此"ANSI/ISO标准”是一种正确的说法。
    那么,这种标准对你有什么帮助呢?你可以买到一份该标准的副本,即Herbert Schildt所著的((The Annotated ANSI C Standard》(Osborne McGraw-Hill出版,ISBN O-07-881952-O)一书,该书对语言和库都作了介绍,并带有注释。这本书比大多数正式标准要便宜多了,后者由ANSI和ISO出售,以解决建立标准所需的部分费用。并不是每一个C程序员都需要这样一本书,但它是最权威的。
    最重要的一点是,ANSI/ISO标准是对“什么是c?”这一问题的权威解答。假如编译程序开发商所做的某些实现不符合这一标准,你可以把它作为错误指出来,这不会引起争论。
    ANSI/ISO标准也不是包罗万象的。具体地说,它没有涉及c程序可能会做的许多有趣的事情,例如图形或多任务。许多兼容性不强的标准包含了这些内容,其中的一些将来可能会成为权威的标准,因此你不必完全拘泥于ANSI/ISO标准。
    顺便提一句,除编程语言之外,还有许多东西也有ANSI标准,其中的一种就是ANSI为全屏幕文本操作的退出序列集合而写的标准,在第17章中所介绍的MS—DOS的"ANSI驱动程序”指的就是这种标准(有趣的是,MS-DOS的ANSI.SYS只实现了ANSI标准序列中的一小部分)。

    16.1  运算符的优先级总能起作用吗?
    有关运算符优先级的规则稍微有点复杂。在大多数情况下,这些规则确实是你所需要的,然而,有人也指出其中的一些规则本来是可以设计得更好的。
    让我们快速地回顾一些有关内容:“运算符优先级”是这样一些规则的集合——这些规则规定了“运算符”(例如+,-,等等)的优先性,即哪一种运算符先参加运算。在数学中,表达式“2×3+4×5”和“(2×3)+(4×5)”是等价的,因为乘法运算在加法运算之前进行,也就是说乘法的优先级比加法高。
    在c中,有16级以上的运算符优先级。尽管这么多的规则有时使c程序不易阅读,但也使C程序写起来轻易多了。虽然这不是唯一的一种折衷方法,但这就是C所采用的方法。表16.1总结了运算符的优先级。

           表16.1 运算符优先级总结(从高到低)
----------------------------------------------------------------------------------
  优先级            运算符
----------------------------------------------------------------------------------
  1                x[y](下标)
                   x(y)(函数调用)
                   x.y(访问成员)
                  x->y(访问成员指针)
                   x++(后缀自增)
                   x--(后缀自减)--
    2             ++x(自增)
                   --x(自减)
                  &x(取地址)
                   *x(指针引用)
                  +x(同x,和数学中相同)
                  -x(数学求负)
                  !x(逻辑非)
                  ~x(按位求反)
                   sizeof x和sizeof(x_t)(字节数大小)
   3               (x_t)y(强制类型转换)
   4              x*y(乘法)
                   x/y(除法)
                   x%y(求余)
   5              x+y(加法)
                   x-y(减法)
   6              x<<y(按位左移)
                   x>>y(按位右移)
    7              x<y,x>y,x<=y,x>=y(关系比较)
    8             x==y,x!=y(相等比较)
    9              x&y(按位与)
    10             x^y(按位异或)  .
    11             x | y(按位或)
    12            x&&y(逻辑与)
    13             x||y(逻辑或)
    14             x?y:z(条件)
                  x=y,x*=y,x/=y,x+=y,x-=y,<<=,>>=,&=,^=,|=(赋值,右结合性)
    16    x,y(逗号)
--------------------------------------------------------------------------------------
    优先级最高的是后缀表达式,即运算符跟在一个表达式后面;其次是前缀或单目表达式,即运算符位于一个表达式的前面;再次是强制类型转换表达式。
    注重:关于运算符优先级,最重要的是知道*p++和*(p++)是等价的。也就是说,在*p++中,++运算符作用在指针上,而不是作用在指针所指向的对象上。象“*p++=*q++;这样的代码在C中是随处可见的,其中的优先级和“(*(p++))=(*(q++))”中的是相同的。这个表达式的含义是“q+1,但仍用q原来的值找到q所指向的对象;p加1,但仍用p原来的值;把q所指向的对象赋给p所指向的对象”,整个表达式的值就是原来q所指向的对象。在C中你会经常看到这样的代码,并且你会有许多机会去写这样的代码。对于其它运算符,假如你记不住其优先级,可以查阅有关资料,但是,一个好的c程序员应该连想都不用想就能明白*p++的含义。
    最初的C编译程序是为这样一种计算机编写的——它的某些指令对象*p++和*p++=*q++这样的代码的处理效率高得令人难以置信,因此,很多C代码就写成这种形式了。进一步地,因为象这样的C代码实在太多了,所以新机型的设计者会保证提供能非常高效地处理这些C代码的指令。
    再下一级的优先级是乘法、除法和求余(也叫取模),再往后是加法和减法。与数学中的表达式相同,“2*3+4*5”和“(2*3)+(4*5)”是等价的。
    再下一级是移位运算。
    再往后两级分别是关系比较(例如x<y)和相等比较(x==y和x!=y)。
    再往后三级分别是按位与、按位异或和按位或。
    注重:关于运算符优先级,再次重要(即在知道*p++和x=y=z的含义之后)的是要知道x&y==z和(x&y)==z是不一样的。因为按位操作的运算符的优先级低于比较运算符,所以x&y==z和x&(y==z)是等价的。这两个表达式的含义都是“先看y和z是否相等(相等为1,不等为0),然后让比较结果和x进行按位与运算”,这与“先让x和y进行按位与运算,再比较其结果是否等于z”相差甚远。有人可能会争辩,按位与运算符的优先级应该高于比较 运算符,但为时已晚,因为相应的标准是早在二十年前被定义的。假如你想把按位与的结果与别的东西进行比较,你就需要使用括号。
    再往后两级是逻辑运算符,例如x&&y和x||y。注重,逻辑与(AND)运算符的优先级高于逻辑或(OR)运算符,这与人们讲话的方式是一致的。例如,请看下面的代码:
    if(have_ticket&&have_reservation
    ||have_money && standby_ok){
    goto_airport();
    }
    这段代码的含义可以这样来描述:“假如你有机票并且预定了航班,或者你有钱并且可以买到备用票,那么你就可以出发去机场了。”假如你用括号改变优先级,你就会得到一种截然不同的条件:
    /* not a recommended algorithm!*/
    if(have_ticket
    &&(have_reservation || have_money)
    &&standby_ok){
    goto airport ();
    }
    这段代码的含义可以这样来描述:“假如你有机票,并且你预定好了航班或者有钱,并且可以买到备用票,那么你就可以出发去机场了。”
    再下一级是条件表达式,例如x?y:z。这是一个if-then-else结构的表达式,而不是一条语句。条件表达式有时可以使程序简洁,有时也会造成语意的模糊。条件表达式具有右结合性,也就是说
    a?b:c?d:e
等价于
  a?b:(c?d:e)
这一点与else—if结构很相似。
   再下一级是赋值运算。所有的赋值运算符都具有相同的优先级。与C的其它双目运算符不同,赋值运算具有“右结合性”,即它是从右向左进行的,而不是从左向右进行的。x+y+z等价于(x+y)+z,x*y+z等价于(x*y)+z,而x=y=z等价于x=(y=z)。
   注重:关于运算符优先级,次重要(即在知道*p++的含义之后)的是要知道x=y=z的含义。因为赋值运算具有右结合性,所以这个表达式等价于x=(y=z),其含义是“将z的值赋给y,然后再将该值赋给x”。象a=b=c=d=O;
   这样的代码是很常见的,按从右向左的顺序,它把。赋给d,再赋给c,再赋给b,最后赋给a。
    c中优先级最低的是逗号运算符。它连接两个表达式,先计算第一个表达式的值,扔掉后,再计算第二个表达式的值。只有当第一个表达式具有副作用时,例如赋值或函数调用,使用逗号运算符才有意义。逗号和赋值运算符经常在for循环语句中搭配使用:
    for(i=0,count=O;i<MAX;++i){
       if(interestmg(a[i])){
       ++count:
       }
    }

    请参见:
    1.6  除了在for语句中之外,在哪些情况下还要使用逗号运算符?
    1.12 运算符的优先级总能保证是“自左至右”或“自右至左”的顺序吗?
    1.13 ++var和var++有什么区别?
    1.14 取模运算符“%”的作用是什么?
    2.13 什么时候应该使用类型强制转换(type cast)?
    2.14 什么时候不应该使用类型强制转换(type cast)?
    7.1  什么是间接引用(indirection)?

    16.2  函数参数类型必须在函数参数表中或紧跟其后的部分中说明吗?
   
函数参数必须在参数表中说明,除非你使用的是一种过时的编译程序,在这种情况下,你应该通过#ifdef指令来同时实现两种可能的说明方式。
    定义函数有两种方法。例如,以fool()和foo2()这样两个函数为例,它们都以一个字符指针作为参数,并且返回一个整型值。假设它们是按如下形式定义的:
    /* old style*/
    int
    foo1(p)
    char *p;
    {
       /*body ot function goes here*/
    }
    /*new style*/
    int
    foo2(char *p)
    {
    /*body of function goes here*/
    }   
    旧方式的唯一好处在于当参数表很长时它显得更美观。
    新方式的好处在于它在提供函数定义的同时,还提供了函数原型。这样,在定义了foo2()以后,假如相同的“.c”文件中有对foo2()的调用,编译程序就会根据定义中的参数检查函数调用中的参数。假如参数不匹配,编译程序就会报告出现严重错误(标准并不要求有这一步,但大多数编译程序中都有)。假如函数调用中的参数可以被转换为定义中的参数,它们就会被转换。只有当函数按新方式定义或使用了函数原型时,才会进行以上处理。假如函数按旧方式定义,或者没有使用函数原型,那么就不会进行参数转换,而且很可能也不会进行参数检查。
    新方式的唯一缺陷在于至今仍有不支持它的编译程序(这些大多数是基于UNIX的编译程序,它们随操作系统一起提供给用户,并且不另外收费。另一方面,许多版本的UNIX也提供了遵循ANSI标准的C编译程序)。
    假如你可能需要使用ANSI标准以外的C编译程序,你最好使用一个宏,它可以在支持函数原型和新的函数定义方式时被定义。在知道能支持函数原型的情况下,你可以让一个相应的头文件自动定义该宏:   
    #ifdef    __ANSI__
    #ifndef USE_PROTOS
    #define USE_PROTOS 1
    #endif
    #endif   
    函数说明可以是这样的:
    #ifdef USE_PROTOS
    int fool(char*);
    Int foo2(char*);
    #else
    int foo1();
    int foo2():
    #endif
    函数定义可以是这样的:
    int
    #ifdef USE_PROTOS
    foo1(char *p)
    #else
    foo1(p)
    char *p;
    #endif
    {
    /*body of function goes here*/
    }
    假如你的软件只运行在MS-DOS,MS-Windows或Macintosh个人计算机上,你就不必考虑旧方式,只管用新方式好了。

    请参见:
    8.1  什么时候说明函数?
    8.2  为什么要使用函数原型?
    14.10 函数参数的类型必须在函数头部或紧跟在其后说明吗?为什么?

    16.3  程序中必须包含main()的原型吗?
   
在14.11中,曾经回答了类似的一个问题,这里从另一个角度回答这个问题。
    main()是一个函数,在大多数情况下,它与别的函数相同。但是,在定义main()时,其参数列表至少有两种可能:
    int main(void);
(无参数)或
    int main(int argc,char **argv);
    注重:main()的参数不必被叫做argc和argv,但它们几乎总是使用这两个名字。与给main()的参数起新名字相比,还有许多地方更值得你去发挥聪明才智。
    在第二种情况下,argc是运行时传递给程序的参数个数;argv[0]是程序名;argv[1]到argv[argc-1]是传递给程序的参数,即命令行的各个参数;argv[argc]是一个空指针。
    main()还有其它形式的合法定义,例如:
    int main(int argc,char**argv,char**envp)。
    其中,envp是一个与getenv()所使用的相同的环境列表。与argv一样,envp也以一个空
指针结束。
    没有一个原型可以匹配main()的所有合法定义,标准规定编译程序不必为main()提供一个原型,从这个角度来看,你也不必这样做。但是,假如你的程序需要使用main()函数的参数,则应该包含相应的main()函数原型。
    没有原型,程序就不可能明确地调用main()进行参数检查。尽管这样的调用并没有被标准所禁止,但它很可能不是一个好主意。
    注重:C++程序明确被禁止调用main()(有些编译程序答应你这样做,但它们是错误的)。c++编译程序在main()中加入了一些奇妙的代码,从而可以初始化(“构造”)全局变量。
假如一个C++程序可以执行两次main(),这种初始化就会发生两次,这恐怕是一件坏事。

    请参见:
    8.2为什么要使用函数原型?
    14.11程序应该总是包含main()的一个原型吗?

    16.4  main()应该总是返回一个值吗?
    当然,除非它调用了exit()。
    当一个程序运行时,它在结束时通常会带有某种指示成功的信息或错误码。一个c程序会用以下两种方式中的一种(或两种)来处理这些指示信息,它们的效果是相同的:
    ·从main()返回一个值(表示成功或失败的代码)。
    ·调用exit(),把表示成功或失败的代码作为参数传递给exit()。
    假如程序“在main()的末尾结束”而没有采取以上处理,那么就无法保证表示成功或失败的代码是什么了,这是一件坏事。
    在写c程序时,你最好快速地检查一下main()函数,它的最后一条语句应该是return语句或者是对exit()的调用(唯一的例外是当最后一条语句永远不会结束时,例如一个不带break语句的无穷for循环。在这种情况下,编译程序会提醒你此后加入到main()函数尾部的
语句永远不会被执行)。

    请参见:
    8.9 exit()和return有什么不同?
    14.12 main()应该总是返回一个值吗?

视频教程列表
文章教程搜索
 
C语言程序设计推荐教程
C语言程序设计热门教程
看全部视频教程
购买方式/价格
购买视频教程: 咨询客服
tel:15972130058