在之前几节我们讲过数据类型、讲过函数、讲过代码执行顺序以及一些添加简单函数的方法。
这一节我们将着重讲讲运算符。包括运算符的含义以及优先级的概念
在C语言中,以下运算符是被公认的:
优先级 | 运算符 | 名称以及含义 | 运算目 | 使用示例 | 结合方向 | 可否重载 | 附加说明 |
无 | () | 圆括弧 | 单目 | (表达式) | 无 | 否 | 括弧内的表达永远先计算 |
无 | dynamic_cast <>() |
类型动态转化 | 单目 | dynamic_cast <目标类型>(源) |
无 | 否 | C++专有,不能转换返回空 |
无 | static_cast <>() |
类型静态转化 | 单目 | static_cast <目标类型>(源) |
无 | 否 | C++专有,不检测转换【注1】 |
无 | reinterpret_cast <>() |
类型解读转化 | 单目 | reinterpret_cast <目标类型>(源) |
无 | 否 | C++专有,强制转换【注1】 |
无 | const_cast <>() |
去常量转化 | 单目 | const_cast <目标类型>(源) |
无 | 否 | C++专有,可将常量转非常量 |
0 | :: | 作用域解析 | 双目 | 域名::目标名 | 从左向右 | 否 | C++专有 |
1 | [] | 下标运算符 | 双目 | 数组名[表达式] | 从左向右 | 可 | 两种语言都有 |
1 | () | 函数调用 | 双目 | 函数名(参数) | 从左向右 | 可 | 两种语言都有 |
1 | . | 对象成员选择 | 双目 | 对象.成员 | 从左向右 | 否 | 两种语言都有 |
1 | -> | 指针成员选择 | 双目 | 对象指针->成员 | 从左向右 | 否 | 两种语言都有 |
1 | {} | 组合运算 | 未知 | {语句1;语句2;…语句n;} | 从左向右 | 否 | 两种语言都有 |
2 | ++ | 自增运算 | 单目 | 变量++ ++变量【注2】 |
从左向右 从右向左 |
可 | 两种语言都有 |
2 | -- | 自减运算 | 单目 | 变量-- --变量【注2】 |
从左向右 从右向左 |
可 | 两种语言都有 |
2 | + | 正号 | 单目 | +变量 | 从右向左 | 可 | 两种语言都有 |
2 | - | 负号 | 单目 | -变量 | 从右向左 | 可 | 两种语言都有 |
2 | ! | 逻辑非 | 单目 | !表达式 | 从右向左 | 可 | 两种语言都有 |
2 | ~ | 按位取反 | 单目 | ~变量 | 从右向左 | 可 | 两种语言都有 |
2 | * | 指针解引用 | 单目 | *指针 | 从右向左 | 可 | 两种语言都有 |
2 | & | 取地址 | 单目 | &变量 | 从右向左 | 可 | 两种语言都有 |
2 | (类型) | 强制类型转换 | 单目 | (类型)源 | 从右向左 | 可 | 两种语言都有 |
2 | sizeof | 获取变量占用 空间大小内存 |
单目 | sizeof(源) | 从右向左 | 否 | 两种语言都有 |
2 | new/new[] | 分配内存 | 单目 | new 类型名 或 new[]类型名 | 从右向左 | 可 | C++专有 |
2 | delete/delete[] | 释放内存 | 单目 | delete 指针 或者 delete[]指针 | 从右向左 | 可 | C++专有 |
3 | .* | 成员对象选择 | 双目 | 对象.*成员名 或 对象.*成员指针 |
从左到右 | 可 | C++专有 |
3 | ->* | 成员指针选择 | 双目 | 对象指针->*成员名 或 对象指针->.*成员指针 |
从左到右 | 可 | C++专有 |
4 | * | 乘法 | 双目 | 变量1*变量2 | 从左到右 | 可 | 两种语言都有 |
4 | / | 除法 | 双目 | 变量1/变量2 | 从左到右 | 可 | 两种语言都有 |
4 | % | 取余 | 双目 | 变量1%变量2 | 从左到右 | 可 | 两种语言都有 |
5 | + | 加法 | 双目 | 变量1+变量2 | 从左到右 | 可 | 两种语言都有 |
5 | - | 减法 | 双目 | 变量1-变量2 | 从左到右 | 可 | 两种语言都有 |
6 | << | 按位左移 | 双目 | 变量1<<变量2 | 从左到右 | 可 | 两种语言都有 |
6 | >> | 按位右移 | 双目 | 变量1>>变量2 | 从左到右 | 可 | 两种语言都有 |
7 | < | 小于 | 双目 | 变量1<变量2 | 从左到右 | 可 | 两种语言都有 |
7 | <= | 不大于 | 双目 | 变量1<=变量2 | 从左到右 | 可 | 两种语言都有 |
7 | > | 大于 | 双目 | 变量1>变量2 | 从左到右 | 可 | 两种语言都有 |
7 | >= | 不小于 | 双目 | 变量1>=变量2 | 从左到右 | 可 | 两种语言都有 |
8 | == | 逻辑等 | 双目 | 变量1==变量2 | 从左到右 | 可 | 两种语言都有 |
8 | != | 不等于 | 双目 | 变量1 != 变量2 | 从左到右 | 可 | 两种语言都有 |
9 | & | 与运算 | 双目 | 变量1&变量2 | 从左到右 | 可 | 两种语言都有 |
10 | ^ | 异或运算 | 双目 | 变量1^变量2 | 从左到右 | 可 | 两种语言都有 |
11 | | | 或运算 | 双目 | 变量1|变量2 | 从左到右 | 可 | 两种语言都有 |
12 | && | 逻辑与 | 双目 | 变量1&&变量2 | 从左到右 | 可 | 两种语言都有 |
13 | || | 逻辑或 | 双目 | 变量1||变量2 | 从左到右 | 可 | 两种语言都有 |
14 | ?: | 条件运算 | 三目 | 变量1?变量2:变量3 | 从右到左 | 否 | 两种语言都有 |
15 | = | 赋值 | 双目 | 变量1=变量2 | 从右到左 | 可 | 两种语言都有 |
15 | += | 加后赋值 | 双目 | 变量1+=变量2 | 从右到左 | 可 | 两种语言都有 |
15 | -= | 减后赋值 | 双目 | 变量1-=变量2 | 从右到左 | 可 | 两种语言都有 |
15 | *= | 乘后赋值 | 双目 | 变量1*=变量2 | 从右到左 | 可 | 两种语言都有 |
15 | /= | 除后赋值 | 双目 | 变量1/=变量2 | 从右到左 | 可 | 两种语言都有 |
15 | %= | 余后赋值 | 双目 | 变量1 %= 变量2 | 从右到左 | 可 | 两种语言都有 |
15 | <<= | 左移赋值 | 双目 | 变量1 <<= 变量2 | 从右到左 | 可 | 两种语言都有 |
15 | >>= | 右移赋值 | 双目 | 变量1 >>= 变量2 | 从右到左 | 可 | 两种语言都有 |
15 | &= | 与后赋值 | 双目 | 变量1 &= 变量2 | 从右到左 | 可 | 两种语言都有 |
15 | ^= | 异或赋值 | 双目 | 变量1 ^= 变量2 | 从右到左 | 可 | 两种语言都有 |
15 | |= | 或后赋值 | 双目 | 变量1 |= 变量2 | 从右到左 | 可 | 两种语言都有 |
16 | throw | 抛异常 | 单目 | throw 变量1 | 从右到左 | 否 | C++专有 |
17 | , | 逗号运算 | 双目 | 变量1,变量2 | 从左到右 | 可 | 两种语言都有 |
注1: reinterpret_cast
<>()和static_cast
<>()貌似都是强制转换,但是两者是不同的
观察下面的代码:
class A { public: A(){ m_a = 1; } int m_a; }; class B { public: B(){ m_b = 2; } int m_b; }; class C : public A, public B { }; void OperatorPriority() { C c; printf("%p, %p, %p\n", &c, reinterpret_cast<B*>(&c), static_cast <B*>(&c)); B* b1 = &c, *b2 = reinterpret_cast<B*>(&c), *b3 = static_cast <B*>(&c); printf("%d, %d, %d\n", b1->m_b, b2->m_b, b3->m_b); }
这是什么意思?
这说明在转换的时候reinterpret_cast
<>()是无脑转换,完全不管前因后果,进行强制转换
而static_cast
<>()会先尝试匹配一下,能匹配则会做出处理,否则则进行强制转换
所以m_b static_cast
<>()会输出正确值,而reinterpret_cast
<>()会给出一个错误值
但是为什么强制类型转换又可以正确输出m_b呢?这就涉及到c++的多态特性,以后我会讲到的。这里只讨论运算符,不做深入讲解。
注2:前缀自增/减运算 和 后缀自增/减运算
前缀自增/减 是这样的 ++变量 --变量
后缀自增/减 是这样的 变量++ 变量--
有很多资料说后缀自增/减运算 优先级高于前缀
这个说得太简单,实际情况非常复杂(也就是说这种说法完全是拍脑袋,没有实践的扯淡!)
首先,来看代码:
从上面的代码可以很明显的看出,如果认为后缀自增运算有所谓的高优先级,那么完全就说不通首先++a--;语句根本就是个语法错误!前缀和后缀运算符不能同时进行,所以也无法比较他们的优先级。
不过这不能说明网上的优先级说明是错误的,因为我们可以拿它们同级的其他运算符进行比较
注意test002函数 !运算是和前缀自增运算同级的运算符,按理说应该是先计算后缀自增然后再计算非,
这样就应该等价于a=!(a++) 即结果应该为0 。但是为什么输出的却是1呢?
答案很复杂。上面真实的运算是这样进行:
先计算计算a++ 即0++=1 保存结果到a 然后按照a=0计算!a即 !0=1,再次保存结果到a 导致的结果就是a最后变成了1
下面是该代码的反汇编
a = !a++; 00113EBD 8B 45 F8 mov eax,dword ptr [a] 00113EC0 89 85 30 FF FF FF mov dword ptr [ebp-0D0h],eax 00113EC6 8B 4D F8 mov ecx,dword ptr [a] 00113EC9 83 C1 01 add ecx,1 00113ECC 89 4D F8 mov dword ptr [a],ecx 00113ECF 83 BD 30 FF FF FF 00 cmp dword ptr [ebp-0D0h],0 00113ED6 75 0C jne main+0B4h (0113EE4h) 00113ED8 C7 85 2C FF FF FF 01 00 00 00 mov dword ptr [ebp-0D4h],1 00113EE2 EB 0A jmp main+0BEh (0113EEEh) 00113EE4 C7 85 2C FF FF FF 00 00 00 00 mov dword ptr [ebp-0D4h],0 00113EEE 8B 95 2C FF FF FF mov edx,dword ptr [ebp-0D4h] 00113EF4 89 55 F8 mov dword ptr [a],edx从表面上看,后缀++好像是优先运算的。但是我们不能只关注这点,而是要关注它对结果的影响
看函数test003 test004
003的计算是这样进行的:先计算a+a 即 0+0 得到0,再计算自增 即0++ 得到1 。最后进行赋值,取a++的值1
下面是实际的计算过程
00DD3E55 8B 45 F8 mov eax,dword ptr [a] 00DD3E58 03 45 F8 add eax,dword ptr [a] 00DD3E5B 89 45 F8 mov dword ptr [a],eax 00DD3E5E 8B 4D F8 mov ecx,dword ptr [a] 00DD3E61 83 C1 01 add ecx,1 00DD3E64 89 4D F8 mov dword ptr [a],ecx也就是说,这里先计算了a+a,然后才计算的a++
这不是说明+运算符的优先级高于后缀++吗!!
004的计算是这样进行的:先计算2+a 即2+0 得到2,再计算自增,即2++,得到3
01383E89 8B 45 F8 mov eax,dword ptr [a] 01383E8C 83 C0 02 add eax,2 01383E8F 89 45 F8 mov dword ptr [a],eax 01383E92 8B 4D F8 mov ecx,dword ptr [a] 01383E95 83 C1 01 add ecx,1 01383E98 89 4D F8 mov dword ptr [a],ecx得到同样的逻辑结果 +运算符优先级高于后缀++
现在我们来总结一下:
后缀运算符 和!运算符在一起的时候,后缀运算符会被屏蔽
后缀运算符和+-*/等运算符在一起的时候,先计算其他,再计算自增
现在你是不是已经彻底糊涂了?
很好,这样就对了。
因为我们现在得到一个结论:
1 那些所谓的优先级,全部是扯淡,千万不要指望优先级能够完全按照你的预想来执行
2 请用圆括弧安排好优先级——只有这个是唯一可靠的伙伴!
下一节开始我将逐个对运算符进行分析和讲解,敬请期待……
原文地址:http://blog.csdn.net/zerglurker/article/details/46489133