您的位置:首页 > 编程语言 > C语言/C++

C/C++中优先级、结合方向与执行顺序的关系

2013-12-12 08:50 316 查看
原文在:http://blog.sina.com.cn/s/blog_93b45b0f0100x47j.html

  知道语言是有以分号结束的语句组成,但语句并不是程序处理的最小单位。理论上讲程序应该以每个函数(方法)为一个语句,而操作符有可以理解成函数。但是那样做的话,程序会很难看,所以通常的语言以表达式为程序的最小执行单位。一个语句本质上说是一个表达式,但是也可以说成是逗号符,分开的若干个表达式,只是有逗号符连接起来的最终也是一个表达式。
  估计表达式的值是判断程序运行对错的核心内容,语句与语句之间的顺序基本右分号;隔开呈现“顺序”的状态,但是语句内部的执行过程却是非常复杂的。
  首先要高清楚三个概念:优先度,结合方向,执行顺序
  三个中最容易让我们忽视的就是执行顺序,通常我们认为结合方向就是执行顺序。
  其实优先级的概念我们都懂,最简单的例子就是+号和*号。我们应该先执行乘号。在每种编程语言中,各种操作符号都有自己的优先度(确定的),他们不会随着着自己的用途(运算符重载)发生变化。因为编译器需要通过优先都来确定混合表达式(多个操作符,多个操作数交杂在一起)中如何关联操作数与操作符。
1+2*3。
  上面的例子就是这样,系统可以根据优先度判断*号的操作符为2和3,而+号的操作符为1和(2*3)。只有这样确定的过程才有一个合理的结果。
所以优先度的定义并不是,哪个操作符更先执行,而是那个操作符可以更先于旁边的操作数结合,虽然结合就是为了操作,但是两者的区别还是很大的。把操作符看做是函数(实际上就是),然后根据函数中函数参数的结合问题来分析表达式的运行结果。
  那么结合方向又是什么呢?这个也是我们容易认为成执行顺序的东西,其实结合方向就是当两个操作符具有同等优先度的时候,谁更有优先度呢!这就是解决类似于:
1+2+3;
i=j=2;
的问题。1+2+3,可能不太重要,但是i=j=2。就太重要了。
  所以优先度和结合方向只是规定符合在一起的操作符与操作数之间如何结合,也就是说那个操作符操作那些操作数,这些必须在编译过程就确定,要不然很多表达式的结果的类型都有可能不确定,这与编译强类型语言的特点相违背。实际上,编译器也是遵守优先级和结合方向来对程序的表达式进行解析的。
  但是!注意,编译器并不执行程序,只是分析程序的结构。从而可以得到某个表达式最终的类型,通过这些类型可以判断程序是否符合某些规则,而并不知道表达式的值。
所以,优先度和结合方向只是为了给将来执行的时候做一个准备,(将函数名和参数理清)而不是要执行。
  所以优先度和结合方向没有那个先执行的暗示。但是由于优先度和结合方向往往决定了一个操作符的结果是另外一个操作符的操作数,那么由于操作数的得到一定先于操作符,所以就间接的安排了很多的执行顺序。
  但是,这并不一定能够安排全部的执行顺序。
i++!=j++;
  上面,根据结合度和优先度,可以看成(i++)!=(j++)。但是你不能说i++会比j++执行的早。或许有些机器会尊从从左到右的执行顺序,但是这不一定。(只是具体的编译器实现,语言层面上并没有做任何规定)。
  或许上面的例子不足以说明执行顺序的不同能改变什么东西。
int a[2];
*a++!=*a;
  根据优先度和结合方向,可以看成(*(a++)!=(*a)。由于你不能确定(*(a++)和(*a)谁先执行,你就不能叛定右边的(*a)中的a的值是多少。这里即使能编译成功,也不一定会按照我们思考的方式运行(虽然通常是按照的)。于是根据这个表达式内部执行顺序的不确定性的特定,我们建议尽量不要再同一个句子中改变某个值,然后又在该表达式中使用该值,除非,能够确定它们的执行顺序(有些方法的)。
  a=++i;就是这样一个例子,它改变i的值,并使用了它,但是由于++i,是a=的操作数,所以他们的执行顺序是确定的。有些数上说不要高于一次的使用,我认为这并不确切,关键是如果你确定它们的执行顺序就可以。
  这既是执行顺序的问题。
  句子之间的顺序有句子来管理。句子内部的顺序则理论上是没有管理的,是乱的。
  优先级和结合方向的目的并不是为了规定执行的顺序,它们的目的是为了确定每个操作符的操作数据(虽然这样一定程度上决定了执行顺序)。
  那么C/C++语言真的没有限制表达式内部执行顺序的东西吗?有的,但是不能覆盖全部。
  (1),逗号运算符,规定必须从左到右执行。
  (2)&&,||这样的逻辑运算符,也是必须从左到右执行,必须先判断左边的。
  (3)?:三目运算符,也是这样的。必须先判断问号前面的,然后向右看。
  逗号运算符这样做,我们就不用管了,主要的原因是如果不怎么做很容易搞混淆。
  逻辑运算符和三目条件运算符这样,使为了让跟好的提高效率。前面我们也作了这方面的讨论。不需要执行的(执行了也没有用),就不要管咯。
  当然也有人认为,这样做会高混淆,其实这就是语言设计者在效率和混淆之间取了一个权衡。
  除了上面上种运算符以外,其他的都不规定执行顺序。

————————————————
以下是2012.6.25新加入的内容
————————————————
  我们现在还差一个问题要解决:
  为什么C/C++要这样呢?为什么不想java中那样,规定表达式中的执行顺序为左到右呢?
  问题变得比较复杂,我们从语言当初设计的角度来看看。
  首先,操作符可以看做是函数,那么复合表达式,就是函数嵌套函数(函数的参数来自函数的另外一个结果)。前面我们提到,优先级和结合方向,让这种函数与函数参数的归宿确定下来。但是我们仍然无法确定,函数的几个参数的先后顺序。
(i++)+(i++);
  好吧,我们最终确定,表达式的执行顺序问题,就是所谓的函数的各个参数的执行先后问题。这个问题在C/C++语言中是不规定的,而在java中是规定从做到右的。
  这样的区别在于这两种语言所要设计达到的目标是不同的,C/C++这样,可以让编译器对一个表达式的执行过程有着较高的自由度,但是这会出现很多的程序员所不能预见的结果;java那样则是为了让程序更为直观,唯一,但是这样也就是放弃了编译器这方面的性能优化的机会。
  那么你会说程序的最高要求是准确,似乎C/C++这样“疯狂”的追求高性能有时候并不能保证准确性。这是有一定道理的。但是,像我们上文讨论过的,只要表达式中,别改变值的变量不被二次应用,那么这个问题就不是问题。
  是这样的,我们在编程序的过程中,一句话(可能是复杂表达式)的最直观的目的应该就是要改变某个值,所以在大部分的程序中,我们只有一个变量被改变,并且这个变量被改变之后是不会再被当前语句所引用的。基于这个常态,那基本上一个表达式中的先后执行顺序基本是不会导致最终结果的不同的。而不定死先后顺序,编译器就可以充分的在上面做文章,这样就可以做到很好的性能优化(比如可以减少都内存的次数)。
  所以,编程的时候,最后要此那种“一句话干一件事”的风格,不要追求一个“复杂的丰富的表达式”。
  一个复杂的表达中,有可能某个变量被改变了(i++,i--),而这个i还在别的地方被引用了(不管是读还是写),这里就有两个情况值得我们去思考。
  (1)即使根据优先级和结合方向,你可以确定这个变量被改变和被应用的先后顺序。程序仍然可能不会按照你想的方向发展。什么意思呢? 我的意思是说:
  即使你能够确定i++,和a[i]先后顺序,你也无法肯定,a[i]中的i是原始值,还是被自增后的值。为什么呢?因为C/C++语言不保证,i++这个语句的“副作用”能够马上被其他地方看见,(可能新的结果还是存在寄存器中),而a[i]则从内存中去取久的值。对于这一定不同的实现,有不同的规定,而C/C++语言上,只规定在某个规定的时间时,必须要做到“副作用”被看见。(通常就是语句结束)。同样这样的结果也一样是为了提供编译器足够的灵活空间去优化代码。
  (2)如果不能通过优先级和结合方向确定,变量被改变和被应用的先后顺序,那么表达式的结果更是一个undefined问题。或许,许多的编译器实现已经给出了一个比较通用的“模式”,但是,作为C/C++设计的最初想法,就是不应该有固定的“模式的”。
  所以仍然还是那句话,在一个句子里面,只做一件事。复杂的表达式必须保证被修改的变量不会再被当前语句应用。否则会出现undefined结果,编程的时候应该尽量避免这种编程风格。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: