在之前几节我们讲过数据类型、讲过函数、讲过代码执行顺序以及一些添加简单函数的方法。
这一节我们将着重讲讲运算符。包括运算符的含义以及优先级的概念
在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);
}
执行OperatorPriority()函数后,输出结果如下:
这是什么意思?
这说明在转换的时候reinterpret_cast
<>()是无脑转换,完全不管前因后果,进行强制转换
而static_cast
<>()会先尝试匹配一下,能匹配则会做出处理,否则则进行强制转换
所以m_b static_cast
<>()会输出正确值,而reinterpret_cast
<>()会给出一个错误值
但是为什么强制类型转换又可以正确输出m_b呢?这就涉及到c++的多态特性,以后我会讲到的。这里只讨论运算符,不做深入讲解。
注2:前缀自增/减运算 和 后缀自增/减运算
前缀自增/减 是这样的 ++变量 --变量
后缀自增/减 是这样的 变量++ 变量--
有很多资料说后缀自增/减运算 优先级高于前缀
这个说得太简单,实际情况非常复杂(也就是说这种说法完全是拍脑袋,没有实践的扯淡!)
首先,来看代码:
void test001()
{
int a = 0;
++a--;//语法错误:++需要左值
}
void test002()
{
int a=0;
a = !a++;
printf("a=%d\n",a);//结果输出1 不论是在VS环境还是GCC环境
}
void test003()
{
int a=0;
a = a+a++;
printf("a=%d\n",a);//结果输出1 不论是在VS环境还是GCC环境
}
void test004()
{
int a=0;
a = 2+a++;
printf("a=%d\n",a);//结果输出3 不论是在VS环境还是GCC环境
}
首先++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
从表面上看,后缀++好像是优先运算的。但是我们不能只关注这点,而是要关注它对结果的影响
运算并不是先计算的就是高优先级的——如果该运算更不不能影响结果
这个表达式的运算a++的结果是被舍弃了,也就是!运算符屏蔽了后缀++!!!
是不是感觉逻辑有点乱?没有关系,我们再看其他函数,这样你就会更加凌乱!
看函数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 请用圆括弧安排好优先级——只有这个是唯一可靠的伙伴!
下一节开始我将逐个对运算符进行分析和讲解,敬请期待……