热门标签 | HotTags
当前位置:  开发笔记 > 前端 > 正文

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

C语言基础教程(我的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 <stdio.h>

        int main(void)
        {
            int var1, var2;

            var1 = var2 = -(9 + 4) * 5 + (6 + 8 * (7 - 1));
            printf("var1 = var2 = %d\n", 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;

        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("%d\n", 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


推荐阅读
  • 2020年9月15日,Oracle正式发布了最新的JDK 15版本。本次更新带来了许多新特性,包括隐藏类、EdDSA签名算法、模式匹配、记录类、封闭类和文本块等。 ... [详细]
  • 使用 Git Rebase -i 合并多个提交
    在开发过程中,频繁的小改动往往会生成多个提交记录。为了保持代码仓库的整洁,我们可以使用 git rebase -i 命令将多个提交合并成一个。 ... [详细]
  • Manacher算法详解:寻找最长回文子串
    本文将详细介绍Manacher算法,该算法用于高效地找到字符串中的最长回文子串。通过在字符间插入特殊符号,Manacher算法能够同时处理奇数和偶数长度的回文子串问题。 ... [详细]
  • Nvidia Ansel 工具为 PC 玩家提供了便捷的高精度图像采集和分享功能。本文介绍了如何将 Ansel 插件集成到虚幻引擎 4 (UE4) 游戏中,并详细说明了其主要功能和系统要求。 ... [详细]
  • malloc 是 C 语言中的一个标准库函数,全称为 memory allocation,即动态内存分配。它用于在程序运行时申请一块指定大小的连续内存区域,并返回该区域的起始地址。当无法预先确定内存的具体位置时,可以通过 malloc 动态分配内存。 ... [详细]
  • 优化虎牙直播体验的插件
    近期在观看虎牙直播时,发现广告和一些低质量直播间频繁出现,严重影响了观看体验。为此,我开发了一款插件,帮助用户屏蔽这些不想要的内容。以下是插件的介绍和使用方法。 ... [详细]
  • 本文介绍了多种开源数据库及其核心数据结构和算法,包括MySQL的B+树、MVCC和WAL,MongoDB的tokuDB和cola,boltDB的追加仅树和mmap,levelDB的LSM树,以及内存缓存中的一致性哈希。 ... [详细]
  • Python多线程详解与示例
    本文介绍了Python中的多线程编程,包括僵尸进程和孤儿进程的概念,并提供了具体的代码示例。同时,详细解释了0号进程和1号进程在系统中的作用。 ... [详细]
  • 深入解析Django CBV模型的源码运行机制
    本文详细探讨了Django CBV(Class-Based Views)模型的源码运行流程,通过具体的示例代码和详细的解释,帮助读者更好地理解和应用这一强大的功能。 ... [详细]
  • 本文将介绍如何在混合开发(Hybrid)应用中实现Native与HTML5的交互,包括基本概念、学习目标以及具体的实现步骤。 ... [详细]
  • 数字经济浪潮下企业人才需求变化,优质IT培训机构助力技能提升
    随着云计算、大数据、人工智能、区块链和5G等技术的迅猛发展,数字经济已成为推动经济增长的重要动力。据信通院数据,2020年中国数字经济占GDP比重达38.6%,整体规模突破39.2万亿元。本文探讨了企业在数字化转型中对技术人才的需求变化,并介绍了优质IT培训机构如何助力人才培养。 ... [详细]
  • 本文详细介绍了Linux系统中用于管理IPC(Inter-Process Communication)资源的两个重要命令:ipcs和ipcrm。通过这些命令,用户可以查看和删除系统中的消息队列、共享内存和信号量。 ... [详细]
  • 一个初秋的雨夜,我独自漫步在校园的小道上,心中突然涌起对理想爱情的憧憬。这篇文章将分享我对理想伴侣的期望,以及与他共度美好时光的愿景。 ... [详细]
  • A*算法在AI路径规划中的应用
    路径规划算法用于在地图上找到从起点到终点的最佳路径,特别是在存在障碍物的情况下。A*算法是一种高效且广泛使用的路径规划算法,适用于静态和动态环境。 ... [详细]
  • 对于众多创业公司而言,选择小程序或小视频的发展方向至关重要。本文将深入分析小程序和小视频的特点、优势及局限,帮助创业者做出更明智的选择。 ... [详细]
author-avatar
我的明天谁2502931447
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有