当前位置:  编程技术>c/c++/嵌入式

C 语言基础教程(我的C之旅开始了)[九]

    来源: 互联网  发布时间:2014-10-11

    本文导语:  24. +、-、*、/、= 的优先级 1. 优先级     和数学一样,C 语言规定先乘除后加减。也就是说,乘法运算符和除法运算符的优先级(Precedence)比加法运算符和减法运算符高。同时,C 语言也规定,如果两个运算符的优先级相...

24. +、-、*、/、= 的优先级

1. 优先级

    和数学一样,C 语言规定先乘除后加减。也就是说,乘法运算符和除法运算符的优先级(Precedence)比加法运算符和减法运算符高。同时,C 语言也规定,如果两个运算符的优先级相同,并且它们之间没有被优先级比它们高或者低的运算符隔开,则它们的运算顺序根据它们在语句中出现的先后而定。大多数运算符都是从左向右进行运算的,不过也有从右向左进行运算的(例如赋值运算符)。乘法运算符和除法运算符的优先级相同,加法运算符和减法运算符的优先级相同。因此,以下语句

        var = 8.0 + 20.0 / 4.0 * 2.0;

运算顺序为:

        20.0 / 4.0
        5.0 * 2.0  (20.0 / 4.0 得 5.0)
        8.0 + 10.0
        var = 18.0

在这个表达式中,/ 和 * 优先级相同,而且是从左向右进行运算的,所以先运算 20.0 / 4.0,然后才轮到 5.0 * 2.0。

    如果我们想让加法先进行,可以给 8.0 + 20.0 加上括号:

        var = (8.0 + 20.0) / 4.0 * 2.0;

这个语句的运算顺序为:

        8.0 + 20.0
        28.0 / 4.0
        7.0 * 2.0
        var = 14.0

    C 语言规定,先进行括号里面的运算,后进行括号外面的运算。在括号里面,运算顺序和上面讨论的一样。例如:

        var = (8.0 + 20.0 / 4.0 * 2.0) / 3.0;

运算顺序为:

        20.0 / 4.0
        5.0 * 2.0
        8.0 + 10.0
        18.0 / 3.0
        var = 6.0

下表总结了这几个运算符的优先级以及它们的结合律,按优先级从高到低进行排列

        运算符             结合律

          ()               从左向右
       + -(单目)         从右向左
         * /               从左向右
       + -(二目)         从左向右
          =                从右向左


2. 优先级和运算顺序

    运算符优先级(Operator precedence)是决定运算顺序的重要规则,但不能完全(也没必要完全)确定运算顺序。例如:

        5 * 3 + 8 * 4;

根据运算符优先级,我们知道,乘法运算先于加法运算。但是 5 * 3 和 8 * 4 谁先谁后,我们并不能确定。它们运算的先后是由编译器决定的。这是因为某种运算顺序在某种系统中效率更高,而另一种运算顺序在另一种系统中效率更高。无论它们的运算先后如何,最终得到的结果都是 47。

    您可能会说:“乘法不是从左向右进行运算的吗?这不是说明最左边的乘法最先进行吗?”是的,乘法的确是从左向右进行运算,但是您也要看到,这两个乘法运算符之间被加法运算符隔开了!我们举一个例子来说明乘法从左向右进行运算的意思。以下语句

        5 * 2 * 9 * 4;

运算顺序为:

        5 * 2
        10 * 9
        90 * 4


下面我们来看一个小程序。

        /* precedence.c -- 优先级测试 */
        #include

        int main(void)
        {
            int var1, var2;

            var1 = var2 = -(9 + 4) * 5 + (6 + 8 * (7 - 1));
            printf("var1 = var2 = %dn", var1);

            return 0;
        }

请认真阅读以上程序,想想会出现什么结果,然后编译运行,看看运行结果和您想象的是否一样。

    首先,括号运算符优先级最高。但是 (9 + 4) 和 (6 + 8 * (7 - 1)) 运算的先后是由编译器决定的。假设 (9 + 4) 先进行,则运算后得 13,然后负号运算符作用于 13 得 -13。于是我们得到:

        var1 = var2 = -13 * 5 + (6 + 8 * (7 - 1));

在 (6 + 8 * (7 - 1)) 中,先运算 (7 - 1),得:

        var1 = var2 = -13 * 5 + (6 + 8 * 6);

因为 * 优先级高于 +,于是我们得到:

        var1 = var2 = -13 * 5 + (6 + 48);

进而

        var1 = var2 = -13 * 5 + 54;
        var1 = var2 = -65 + 54;
        var1 = var2 = -11;

因为赋值运算是从右向左的,所以 -11 被赋值给 var2,接着 var2 被赋值给 var1。最终的结果是,var1 和 var2 相等,它们的值都是 -11。


 

25. 模除运算符 %

 

    % 是模除运算符(Modulus Operator),用于求余数。% 只可用于对整数进行模除,不可用于浮点数。例如:

          15 % 2       // 正确。余数为 1
          15.2 % 3     // 错误!

C99 以前,并没有规定如果操作数中有负数,模除的结果会是什么。C99 规定,如果 % 左边的操作数是正数,模除的结果也是正数;如果 % 左边的操作数是负数,模除的结果就是负数。例如:

          15 % 2       // 余 1
          15 % -2      // 余 1
          -15 % 2      // 余 -1
          -15 % -2     // 余 -1

标准规定,如果 a 和 b 都是整数,则 a % b 可以用公式 a - (a / b) * b 算出。例如:

          -15 % 2 == -15 - (-15 / 2) * 2 == -15 - (-7) * 2 == -1

最后,我们看一个小程序。

        /* months_to_year.c -- 将用户输入的月数转换成年数和月数 */

        #include

        int main(void)
        {
            int months, years, months_left, months_per_year = 12;

            printf("Enter the number of months: ");
            scanf("%d", &months);

            years = months / months_per_year;         /* 算出年数 */
            months_left = months % months_per_year;   /* 算出剩余的月数 */

            printf("%d months is %d years, %d months.n", months, years, months_left);

            return 0;
        }

26. 自增运算符和自减运算符
 

1. 自增运算符(Increment Operator)

    自增运算符 ++ 使操作数的值增 1。++ 可以置于操作数前面,也可以放在后面。例如:

        ++n ;
        n++ ;

这两个语句产生的结果都是使 n 增 1,可以说没什么区别。使用以下语句得到的效果也是一样的:

        n = n + 1 ;

    尽管上面两个语句中,++ 前置和后置没有区别。但是,++ 前置和后置其实是有区别的。例如:

        int n = 1, post, pre;

        post = n++;
        pre = ++n;

对于 post = n++; 这个语句,n 的值被赋予 post 后,n 才增 1。也就是说,这个语句执行完后,post 的值是 1,而 n 的值变成 2。而 pre = ++n; 这个语句,n 先增 1,然后再把自增后的值赋予 pre。也就是说,这个语句执行完后,pre 的值是 3,n 的值也是 3。

    由此可得,如果 ++ 前置,则 ++ 的操作数先增 1,然后再参与其它运算;如果 ++ 后置,则 ++ 的操作数先参与其它运算,然后才增 1。严格地说,前置 ++ 的操作数的值在被使用之前增 1,而后置 ++ 的操作数的值在被使用之后增 1。例如:

        int n = 5, post = 1, pre = 1;
        pre = ++n + pre;    // 运算结束后 pre 为 7
        n = 5;
        post = n++ + post;  // 运算结束后 post 为 6

 

2. 自减运算符(Decrement Operator)

    自减运算符 -- 使操作数的值减 1。-- 可以置于操作数前面,也可以放在后面。例如:

        --n ;
        n-- ;

自减运算符和自增运算符非常相似,区别只在于自减运算符使操作数减 1,而自增运算符使操作数增 1。例如:

        int n = 5, post = 1, pre = 1;
        pre = --n + pre;    // 运算结束后 pre 为 5
        n = 5;
        post = n-- + post;  // 运算结束后 post 为 6

 

3. 优先级

    自增运算符和自减运算符的优先级很高,只有圆括号的优先级比它们高。因此,n*m++; 表示 n*(m++); 而不是 (n * m)++; 。而且 (n * m)++; 是错误的。因为 ++ 和 -- 的操作数只能是可变左值(modifiable lvalue),而 n * m 不是。

    注意,不要把优先级和取值顺序混淆了。例如:

        int x = 1, y = 2, z;

        z = (x + y++) * 3;   // 运算结束后 z 为 9,y 为 3

用数字代替上面的语句得:

        z = (1 + 2) * 3;

仅当 y 的值被使用后,y 才会增 1。优先级表明的是 ++ 仅作用于 y,而不是 (x + y)。优先级也表明 y 的值何时被使用,但是 y 的值何时增 1 是由自增运算符的本质决定的。

    当 y++ 是某个算术表达式的一部分时,您可以认为它表示“先使用 y 的值,然后自增”。类似地,++y 表示“先自增,然后使用自增后的值”。

==========================================================================

以下内容引自《C 语言常见问题集》 原著:Steve Summit 翻译:朱群英, 孙 云

http://c-faq-chn.sourceforge.net/ccfaq/index.html

http://www.eskimo.com/~scs/C-faq/top.html

==========================================================================

4.3 对于代码  int i = 3; i = i++; 不同编译器给出不同的结果, 有的为 3, 有的为 4, 哪个是正确的?

    没有正确答案;这个表达式无定义。参见问题 3.1, 3.7 和  11.32。 同时注意, i++ 和 ++i 都不同于 i+1。如果你要使 i 自增 1, 使用 i=i+1, i+=1, i++ 或 ++i, 而不是任何组合, 参见问题 3.10

12.35 有人说 i = i++ 的行为是未定义的, 但是我刚在一个兼容 ANSI  的编译器上测试, 得到了我希望的结果。

    面对未定义行为的时候, 包括范围内的实现定义行为和未确定行为, 编译器可以做任何实现, 其中也包括你所有期望的结果。但是依靠这个实现却不明智。参加问题 7.4, 11.31, 11.32 和 11.34

4.2 使用我的编译器,下面的代码  int i=7; printf("%dn", i++ * i++); 返回 49?不管按什么顺序计算, 难道不该打印出56吗?

    尽管后缀自加和后缀自减操作符 ++ 和 -- 在输出其旧值之后才会执行运算, 但这里的``之后"常常被误解。没有任何保证确保自增或自减会在输出变量原值之后和对表达式的其它部分进行计算之前立即进行。也不能保证变量的更新会在表达式 ``完成" (按照 ANSI C 的术语, 在下一个 ``序列点" 之前, 参见问题 3.7) 之前的某个时刻进行。本例中, 编译器选择使用变量的旧值相乘以后再对二者进行自增运算。

    包含多个不确定的副作用的代码的行为总是被认为未定义。(简单而言, ``多个不确定副作用" 是指在同一个表达式中使用导致同一对象修改两次或修改以后又被引用的自增, 自减和赋值操作符的任何组合。这是一个粗略的定义; 严格的定义参见问题 3.7, ``未定义" 的含义参见问题 11.32。) 甚至都不要试图探究这些东西在你的编译器中是如何实现的 (这与许多 C 教科书上的弱智练习正好相反); 正如  K&R 明智地指出, ``如果你不知道它们在不同的机器上如何实现, 这样的无知可能恰恰会有助于保护你。

4.7 我怎样才能理解复杂表达式?``序列点" 是什么?

    序列点是一个时间点(在整个表达式全部计算完毕之后或在 ||、  &&、 ? : 或逗号 运算符处, 或在函数调用之前), 此刻尘埃落定, 所有的副作用都已确保结束。 ANSI/ISO C 标准这样描述:

在上一个和下一个序列点之间, 一个对象所保存的值至多只能被表达式的计算修改一次。而且前一个值只能用于决定将要保存的值。

第二句话比较费解。它说在一个表达式中如果某个对象需要写入, 则在同一表达式中对该对象的访问应该只局限于直接用于计算将要写入的值。这条规则有效地限制了只有能确保在修改之前才访问变量的表达式为合法。例如 i = i+1 合法, 而 a[i] = i++ 则非法 (参见问题 3.1)。

参见下边的问题 3.8


    
 
 

您可能感兴趣的文章:

  • 有了c语言基础,如何入手学习用c语言linux网络编程
  • 请问大侠学java要有c语言基础吗?
  •  只有一点JAVA语言基础,但是想先看看关于软件工程方面的东西.可能吗?
  • 请介绍一两本精典的JSP书籍,我要去买了,已有JAVA语言基础!
  • 自己在家学那么3个月到半年UNIX下的网络编程,有C语言基础,出去后能找到这方面的事吗?
  • C语言基础问题,参数定义在函数名后面,是为什么?
  • (基础)linux下c语言程序的编译问题
  • 看看您的c语言基础如何?链表问题
  • SQL语言查询基础:连接查询 联合查询 代码
  • C 语言基础教程(我的C之旅开始了)[五]
  • C 语言基础教程(我的C之旅开始了)[三]
  • C 语言基础教程(我的C之旅开始了)[四] iis7站长之家
  • C语言嵌入informix基础入门示例讲解
  • C 语言基础教程(我的C之旅开始了)[二]
  • C 语言基础教程(我的C之旅开始了)[十]
  • C 语言基础教程(我的C之旅开始了)[四]
  • Oracle PL/SQL语言入门基础
  • C 语言基础教程(一)颜色加亮
  • C 语言基础教程(我的C之旅开始了)[六]
  • python基础教程之简单入门说明(变量和控制语言使用方法)
  • C 语言基础教程(我的C之旅开始了)[七]
  •  
    本站(WWW.)旨在分享和传播互联网科技相关的资讯和技术,将尽最大努力为读者提供更好的信息聚合和浏览方式。
    本站(WWW.)站内文章除注明原创外,均为转载、整理或搜集自网络。欢迎任何形式的转载,转载请注明出处。












  • 相关文章推荐
  • C语言中基础小问题详细介绍
  • Python语言的12个基础知识点小结


  • 站内导航:


    特别声明:169IT网站部分信息来自互联网,如果侵犯您的权利,请及时告知,本站将立即删除!

    ©2012-2021,,E-mail:www_#163.com(请将#改为@)

    浙ICP备11055608号-3