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

2016-02-19 11:49 7 1 收藏

下面,图老师小编带您去了解一下C 语言基础教程(我的C之旅开始了)[九],生活就是不断的发现新事物,get新技能~

【 tulaoshi.com - 编程语言 】

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 stdio.h

        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 stdio.h

        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;

(本文来源于图老师网站,更多请访问http://www.tulaoshi.com/bianchengyuyan/)

        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, 哪个是正确的?

(本文来源于图老师网站,更多请访问http://www.tulaoshi.com/bianchengyuyan/)

    没有正确答案;这个表达式无定义。参见问题 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。

来源:http://www.tulaoshi.com/n/20160219/1598762.html

延伸阅读
if语句 if语句用于测试条件并在条件为真时执行一桌或几条语句。 说明:if表达式后面不能带分号,否则它本身表示代码中的空语句,使编译器将空语句解释为在条件为真时执行的语句。 if (x == 10); // Warning! Extra semicolon! DoSomething(x); 这里DoSomething()函数总会执行,因为编译器不把它看成在条件为真时执行...
终于来到第二章了,真是不容易(呵呵,大家少安毋躁)。 这章总的来说就是对C#的一个总体的概述。首先通过对以往的编程语言(汇编,C/C++,java,vb)的回顾,强调这些语言与C#的联系及C#对这些语言优秀特性的继承,试图在其中打到C#的影子。然后对现在市面上流行的编程语言与C#进行比较,从而突出C#做为新一代的编程语言的重要性及优越性。之...
各位贵安了!(先别骂我)最近刚开学,所以事情就多了点,上网的时间也不多,所以一直这个文章的下一篇也没出来。能得到大家的抬爱,本人甚是感激!对于C#的学习我也是刚刚入门,有什么不对的地方,敬请指教!好了,闲言少叙,进入正题。 第一章。万事开头难,什么事,都得从第一步开始。这一章主要是对.NET的一个概述,使我们对.NET有个概...
从程序流程的角度来看,程序可以分为三种基本结构, 即顺序结构、分支结构、循环结构。 这三种基本结构可以组成所有的各种复杂程序。C语言提供了多种语句来实现这些程序结构。 本文将介绍这些基本语句及其应用,使读者对C程序有一个初步的熟悉, 为以后的学习打下基础。 C程序的语句 C程序的执行部分是由语句组成的。 程序的功能...
第四节、访问接口 对接口成员的访问 对接口方法的调用和采用索引指示器访问的规则与类中的情况也是相同的。如果底层成员的命名与继承而来的高层成员一致,那么底层成员将覆盖同名的高层成员。但由于接口支持多继承,在多继承中,如果两个父接口含有同名的成员,这就产生了二义性(这也正是C#中取消了类的多继承机制的原因之一)...

经验教程

721

收藏

88
微博分享 QQ分享 QQ空间 手机页面 收藏网站 回到头部