c++高级---c语言中关于运算符优先级和运算符集合顺序的理解(*p++和*++p的区别)
2013-05-07 14:48
183 查看
转自:http://blog.csdn.net/zhang429350061/article/details/8775693
接下来,通过示例彻底理解自增运算符的两种用法(自减的用法与之类似,只不过是加1变成了减1)。
1、++i和i++的区别
如清单1(注意代码中的注释):
[cpp] view
plaincopy
#include <stdio.h>
int main(void)
{
int a, b, i = 7;
i++; //等价于i = i + 1;
++i; //等价于i = i + 1;
a = i++; //等价于a = i; i = i + 1;
b = ++i; //等价于i = i + 1; b = i;
printf("a = %d, b = %d\n", a, b);
return 0;
}
例子输出结果:
[cpp] view
plaincopy
a = 9, b = 11
在例子中,第7和第8行的作用一样,仅仅是为变量i加1,这时i的值已经增加为9,接下来第10行变量a先获得i的值(即9),然后i加1,第11行变量i先再加1,然后把得到的值赋给b,所以b的值为11。
稍微复杂的例子,如清单2:
[cpp] view
plaincopy
#include <stdio.h>
int main(void)
{
int a = 5;
int *p = &a;
int b = (*p)++; //等价于b = a++; 即b = a; a = a + 1;
int c = ++(*p); //等价于c = ++a; 即a = a + 1; c = a;
printf("b = %d, c = %d\n", b, c);
printf("(*p)++ = %d, ++(*p) = %d\n", (*p)++, ++(*p));
return 0;
}
例子输出结果:
[cpp] view
plaincopy
b = 5, c = 7
(*p)++ = 8, ++(*p) = 8
在这个例子中,只不过是通过*p来间接地操作a,其他关于自增运算符的用法与清单1类似。第9行的*p一定要用小括号括起来,否则含义就不一样了。而第11行的++(*p)也可以写成++*p(用GCC验证过),那是因为对操作数p来说它只有一个运算符*在计算它,所以无关乎运算符优先级和结合性的问题。
值得注意的是,由于C语言没有指定函数各参数的求值顺序,所以第15行的代码是不可移植的,用不同的编译器可能会产生不同的结果(对于这个例子,GCC是先计算++(*p),后计算(*p)++,所以两者都等于8)。
知识点:
(1)、副作用
在对表达式求值的同时,修改了某些变量的值,其中修改值的行为在C语言中被叫作副作用,那是因为对C语言而言,计算的目的就是对表达式求值,如语句int a = 5,它的含义是先求值得到5,然后把5赋值给变量a,后一步的赋值就是此表达式的副作用。自增和自减运算符就是因为副作用而被使用,除了加1或减1之外,还给自身赋值。
(2)、运算符的优先级
在C语言中,把运算符的优先级分为15级,如下表,从上到下,依次为从最高优先级到最低优先级(为了方便记忆,将15级分成11类,并对每类进行了命名)。
(3)、结合性
对于同一操作数,在具有两个相同优先级的操作符时决定先执行哪个操作符的问题就是由结合性决定的。
相同优先级的操作符具有同样的结合性。右结合性就是说表达式中最右边的操作最先执行,然后从右到左依次执行。在C语言中,具有右结合性的操作符只有相应的三类,分别为一元运算符、条件运算符和赋值运算符。
注意:C语言中的优先级和结合性都是针对同一操作数而言的。如表达式24/8*2,对于操作数8而言,/ 和*的优先级相同,所以再根据它们的左结合性可知,表达式是先计算24/8得到3,然后计算3*2得到6。
C语言并没有规定同一运算符相关的多个操作数的计算顺序(&&、|| 、? : 和 , 运算符除外),如式子a = 8 * 9 + 20 * 4,对操作数9和20而言,根据优先级就可判断先乘后加,但表达式中的两个*并不共享同一操作数,所以从左到右的结合性并不适用它,8 * 9 和20 * 4的计算顺序是不定的,到底先计算8 * 9还是20 * 4由编译器决定。
在上面例子中,8 * 9和20 * 4谁先执行都不影响最后结果的一致,但有些情况下就未必了,如“ b = 3; a = (b++) * (b++); ”这样的例子,对于不同的编译器最后a的值可能等于9,也可能等于12,甚至可能等于16。因此,在实际应用中不能出现这样的未确定性,根据自己的需要,可以把它改成类似“b = 3; c = b++; a = c * c;”这样的形式。
2、*p++和*++p的区别
举例,如清单3:
[cpp] view
plaincopy
#include <stdio.h>
int main(void)
{
int arr[] = {1, 2, 3, 4};
int *p = arr;
int a = *p++; //等价于a = *(p++); 即a = *p; p = p + 1;
int b = *++p; //等价于b = *(++p); 即p = p + 1; b = *p;
printf("a = %d, b = %d\n", a, b);
return 0;
}
例子输出结果:
[cpp] view
plaincopy
a = 1, b = 3
对于第8行的操作数p而言,*和++的优先级相同,但根据它们的右结合性可知,在这个表达式里可认为++的优先级高于*,即*p++等价于*(p++)。
而对于第10行的操作数p而言,它只有一个运算符++,所以先计算++p得出结果,然后间接运算。
【其实对于*p++到底是等价于*p,p++还是p++,*p,可能不同编译器设计的不同,但其结果都一样,不用太纠结,按照上面的理解就行。最主要要记住p++整个表达式返回的是p,然后稍过一段时间即整个语句也即大部分情况下碰到分号“;”后p才++】
最后,附一个更清晰详细的运算符优先级的表:
接下来,通过示例彻底理解自增运算符的两种用法(自减的用法与之类似,只不过是加1变成了减1)。
1、++i和i++的区别
如清单1(注意代码中的注释):
[cpp] view
plaincopy
#include <stdio.h>
int main(void)
{
int a, b, i = 7;
i++; //等价于i = i + 1;
++i; //等价于i = i + 1;
a = i++; //等价于a = i; i = i + 1;
b = ++i; //等价于i = i + 1; b = i;
printf("a = %d, b = %d\n", a, b);
return 0;
}
例子输出结果:
[cpp] view
plaincopy
a = 9, b = 11
在例子中,第7和第8行的作用一样,仅仅是为变量i加1,这时i的值已经增加为9,接下来第10行变量a先获得i的值(即9),然后i加1,第11行变量i先再加1,然后把得到的值赋给b,所以b的值为11。
稍微复杂的例子,如清单2:
[cpp] view
plaincopy
#include <stdio.h>
int main(void)
{
int a = 5;
int *p = &a;
int b = (*p)++; //等价于b = a++; 即b = a; a = a + 1;
int c = ++(*p); //等价于c = ++a; 即a = a + 1; c = a;
printf("b = %d, c = %d\n", b, c);
printf("(*p)++ = %d, ++(*p) = %d\n", (*p)++, ++(*p));
return 0;
}
例子输出结果:
[cpp] view
plaincopy
b = 5, c = 7
(*p)++ = 8, ++(*p) = 8
在这个例子中,只不过是通过*p来间接地操作a,其他关于自增运算符的用法与清单1类似。第9行的*p一定要用小括号括起来,否则含义就不一样了。而第11行的++(*p)也可以写成++*p(用GCC验证过),那是因为对操作数p来说它只有一个运算符*在计算它,所以无关乎运算符优先级和结合性的问题。
值得注意的是,由于C语言没有指定函数各参数的求值顺序,所以第15行的代码是不可移植的,用不同的编译器可能会产生不同的结果(对于这个例子,GCC是先计算++(*p),后计算(*p)++,所以两者都等于8)。
知识点:
(1)、副作用
在对表达式求值的同时,修改了某些变量的值,其中修改值的行为在C语言中被叫作副作用,那是因为对C语言而言,计算的目的就是对表达式求值,如语句int a = 5,它的含义是先求值得到5,然后把5赋值给变量a,后一步的赋值就是此表达式的副作用。自增和自减运算符就是因为副作用而被使用,除了加1或减1之外,还给自身赋值。
(2)、运算符的优先级
在C语言中,把运算符的优先级分为15级,如下表,从上到下,依次为从最高优先级到最低优先级(为了方便记忆,将15级分成11类,并对每类进行了命名)。
初等运算符 | 包括小括号 ()、中括号 [] 、成员访问运算符 . 和 -> 。 |
一元运算符 | 包括自增++和自减--、正负号+ 和-、间接运算*和取址运算& 、类型转换(type)、 sizeof 、逻辑反! 、位取反~等。 |
算术运算符 | 包括两级,先乘除(*、/、%)后加减(+、-)。 |
位移运算符 | 包括左移 << 和右移 >> 。 |
关系运算符 | 包括小于 < 、小于等于 <= 、大于 > 、大于等于 >= 。 |
判等运算符 | 包括相等 == 和不相等 != 。 |
位逻辑运算符 | 分三级,依次为位与 &、位异或 ^ 和位或 | 。 |
逻辑运算符 | 分两级,依次为逻辑与 && 和逻辑或 || 。 |
条件运算符 | ? : |
赋值运算符 | 包括= 、+= 、-=、 *=、 /=、 %= 、&= 、^=、 |= 、<<= 、>>= 。 |
逗号运算符 | , |
对于同一操作数,在具有两个相同优先级的操作符时决定先执行哪个操作符的问题就是由结合性决定的。
相同优先级的操作符具有同样的结合性。右结合性就是说表达式中最右边的操作最先执行,然后从右到左依次执行。在C语言中,具有右结合性的操作符只有相应的三类,分别为一元运算符、条件运算符和赋值运算符。
注意:C语言中的优先级和结合性都是针对同一操作数而言的。如表达式24/8*2,对于操作数8而言,/ 和*的优先级相同,所以再根据它们的左结合性可知,表达式是先计算24/8得到3,然后计算3*2得到6。
C语言并没有规定同一运算符相关的多个操作数的计算顺序(&&、|| 、? : 和 , 运算符除外),如式子a = 8 * 9 + 20 * 4,对操作数9和20而言,根据优先级就可判断先乘后加,但表达式中的两个*并不共享同一操作数,所以从左到右的结合性并不适用它,8 * 9 和20 * 4的计算顺序是不定的,到底先计算8 * 9还是20 * 4由编译器决定。
在上面例子中,8 * 9和20 * 4谁先执行都不影响最后结果的一致,但有些情况下就未必了,如“ b = 3; a = (b++) * (b++); ”这样的例子,对于不同的编译器最后a的值可能等于9,也可能等于12,甚至可能等于16。因此,在实际应用中不能出现这样的未确定性,根据自己的需要,可以把它改成类似“b = 3; c = b++; a = c * c;”这样的形式。
2、*p++和*++p的区别
举例,如清单3:
[cpp] view
plaincopy
#include <stdio.h>
int main(void)
{
int arr[] = {1, 2, 3, 4};
int *p = arr;
int a = *p++; //等价于a = *(p++); 即a = *p; p = p + 1;
int b = *++p; //等价于b = *(++p); 即p = p + 1; b = *p;
printf("a = %d, b = %d\n", a, b);
return 0;
}
例子输出结果:
[cpp] view
plaincopy
a = 1, b = 3
对于第8行的操作数p而言,*和++的优先级相同,但根据它们的右结合性可知,在这个表达式里可认为++的优先级高于*,即*p++等价于*(p++)。
而对于第10行的操作数p而言,它只有一个运算符++,所以先计算++p得出结果,然后间接运算。
【其实对于*p++到底是等价于*p,p++还是p++,*p,可能不同编译器设计的不同,但其结果都一样,不用太纠结,按照上面的理解就行。最主要要记住p++整个表达式返回的是p,然后稍过一段时间即整个语句也即大部分情况下碰到分号“;”后p才++】
最后,附一个更清晰详细的运算符优先级的表:
优先级 | 运算符 | 名称或含义 | 使用形式 | 结合方向 | 说明 |
1 | [] | 数组下标 | 数组名[常量表达式] | 左到右 | |
() | 圆括号 | (表达式)/函数名(形参表) | |||
. | 成员选择(对象) | 对象.成员名 | |||
-> | 成员选择(指针) | 对象指针->成员名 | |||
2 | - | 负号运算符 | -表达式 | 右到左 | 单目运算符 |
(类型) | 强制类型转换 | (数据类型)表达式 | |||
++ | 自增运算符 | ++变量名/变量名++ | 单目运算符 | ||
-- | 自减运算符 | --变量名/变量名-- | 单目运算符 | ||
* | 取值运算符 | *指针变量 | 单目运算符 | ||
& | 取地址运算符 | &变量名 | 单目运算符 | ||
! | 逻辑非运算符 | !表达式 | 单目运算符 | ||
~ | 按位取反运算符 | ~表达式 | 单目运算符 | ||
sizeof | 长度运算符 | sizeof(表达式) | |||
3 | / | 除 | 表达式/表达式 | 左到右 | 双目运算符 |
* | 乘 | 表达式*表达式 | 双目运算符 | ||
% | 余数(取模) | 整型表达式/整型表达式 | 双目运算符 | ||
4 | + | 加 | 表达式+表达式 | 左到右 | 双目运算符 |
- | 减 | 表达式-表达式 | 双目运算符 | ||
5 | << | 左移 | 变量<<表达式 | 左到右 | 双目运算符 |
>> | 右移 | 变量>>表达式 | 双目运算符 | ||
6 | > | 大于 | 表达式>表达式 | 左到右 | 双目运算符 |
>= | 大于等于 | 表达式>=表达式 | 双目运算符 | ||
< | 小于 | 表达式<表达式 | 双目运算符 | ||
<= | 小于等于 | 表达式<=表达式 | 双目运算符 | ||
7 | == | 等于 | 表达式==表达式 | 左到右 | 双目运算符 |
!= | 不等于 | 表达式!= 表达式 | 双目运算符 | ||
8 | & | 按位与 | 表达式&表达式 | 左到右 | 双目运算符 |
9 | ^ | 按位异或 | 表达式^表达式 | 左到右 | 双目运算符 |
10 | | | 按位或 | 表达式|表达式 | 左到右 | 双目运算符 |
11 | && | 逻辑与 | 表达式&&表达式 | 左到右 | 双目运算符 |
12 | || | 逻辑或 | 表达式||表达式 | 左到右 | 双目运算符 |
13 | ?: | 条件运算符 | 表达式1? 表达式2: 表达式3 | 右到左 | 三目运算符 |
14 | = | 赋值运算符 | 变量=表达式 | 右到左 | |
/= | 除后赋值 | 变量/=表达式 | |||
*= | 乘后赋值 | 变量*=表达式 | |||
%= | 取模后赋值 | 变量%=表达式 | |||
+= | 加后赋值 | 变量+=表达式 | |||
-= | 减后赋值 | 变量-=表达式 | |||
<<= | 左移后赋值 | 变量<<=表达式 | |||
>>= | 右移后赋值 | 变量>>=表达式 | |||
&= | 按位与后赋值 | 变量&=表达式 | |||
^= | 按位异或后赋值 | 变量^=表达式 | |||
|= | 按位或后赋值 | 变量|=表达式 | |||
15 | , | 逗号运算符 | 表达式,表达式,… | 左到右 | 从左向右顺序运算 |
相关文章推荐
- c++与java区别的理解(二)--处理参数顺序和输出编译顺序
- 关于typedef的理解在C++和C中的区别
- C语言与C++中点运算符与箭头运算符的区别
- C++中关于类(封装、继承、多态)区别于结构体的理解
- 关于c语言和c++的区别和联系
- 集合中关于iterator遍历顺序的理解
- C++中关于构造函数调用次序的一道经典例题及对虚函数构造顺序的理解
- 关于C语言静态链接的个人理解,欢迎指正
- [转] 关于C++中模板中的typename和class的区别比较
- C语言中关于const与指针结合的理解
- 关于C++访问控制的理解
- 关于集合框架Collection基础知识点的理解掌握
- C++四种强制类型转换运算符的联系与区别
- 【深入理解java集合系列】List,Set,Map用法以及区别
- 关于C++指向指针的指针的一点测试与理解
- C语言与C++中static,const 关键字的区别
- 关于C语言的fprintf与fwrite使用区别
- 关于C和C++区别的讨论
- java 与 c++ 关于局部变量重命名的区别
- C++中类的对象和指针的区别理解