深入理解C/C++中的运算符及它们的优先级规则
运算符
-
-
- 算术运算符
- 位运算符
- 关系运算符
- 逻辑运算符
- 赋值运算符
- 其他运算符 ↦ sizeof & 三目
-
-
- 逗号表达式的说明:
-
- C 中的运算符优先级
- 习题
-
运算符是一种告诉编译器执行特定的数学或逻辑操作的符号。C 语言拥有丰富的运算符,并提供了以下类型的运算符:
- 算术运算符
- 位运算符
- 关系运算符
- 逻辑运算符
- 赋值运算符
- 其他运算符
本章将逐一介绍算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符和其他运算符。
算术运算符
下表显示了 C 语言支持的所有算术运算符。假设变量 A 的值为 10,变量 B 的值为 20,则:
运算符 | 描述 | 实例 |
---|---|---|
+ | 把两个操作数相加 | A + B 将得到 30 |
- | 从第一个操作数中减去第二个操作数 | A - B 将得到 -10 |
* | 把两个操作数相乘 | A * B 将得到 200 |
/ | 分子除以分母 | B / A 将得到 2 |
% | 取模运算符,整除后的余数 | B % A 将得到 0 |
++ | 自增运算符,整数值增加 1 | A++ 将得到 11 |
-- | 自减运算符,整数值减少 1 | A-- 将得到 9 |
实例:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a = 10;
int b = 20;
int c;
c = a + b;
printf("Line 1 - c 的值是 %d\n", c);
c = a - b;
printf("Line 2 - c 的值是 %d\n", c);
c = a * b;
printf("Line 3 - c 的值是 %d\n", c);
c = b / a;
printf("Line 4 - c 的值是 %d\n", c);
c = b % a;
printf("Line 5 - c 的值是 %d\n", c);
c = a++; // 赋值后再加 1 ,c 为 10,a 为 11
printf("Line 6 - c 的值是 %d, a 的值是 %d\n", c, a);
c = a--; // 赋值后再减 1 ,c 为 11 ,a 为 10
printf("Line 7 - c 的值是 %d, a 的值是 %d\n", c, a);
system("pause");
return 0;
}
程序生成图:
验证 a++ 与 ++a 的区别:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a = 10;
int c;
c = a++;
printf("先赋值后运算:\n");
printf("Line 1 - c 的值是 %d, a 的值是 %d\n", c, a);
a = 10;
c = a--;
printf("Line 2 - c 的值是 %d, a 的值是 %d\n", c, a);
a = 10;
c = ++a;
printf("先运算后赋值:\n");
printf("Line 3 - c 的值是 %d, a 的值是 %d\n", c, a);
a = 10;
c = --a;
printf("Line 4 - c 的值是 %d, a 的值是 %d\n", c, a);
system("pause");
return 0;
}
程序生成图:
位运算符
位运算符作用于位,并逐位执行操作。&(按位与)、 |(按位或) 和 ^(按位异或)、~(按位取反操作符) 的真值表如下所示:
p | q | p & q | p | q | p ^ q | ~p |
---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 1 |
0 | 1 | 0 | 1 | 1 | 1 |
1 | 1 | 1 | 1 | 0 | 0 |
1 | 0 | 0 | 1 | 1 | 0 |
假设如果 A = 60,且 B = 13,现在以二进制格式表示,它们如下所示:
A = 0011 1100
B = 0000 1101
则:
A&B = 0000 1100
A|B = 0011 1101
A^B = 0011 0001
~A = 1100 0011
下表为 C 语言支持的移位运算符。假设变量 A 的值为 60,则:
运算符 | 描述 | 实例 |
---|---|---|
<< | 二进制左移运算符。将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。 | A << 2 将得到 240,即为 1111 0000 |
>> | 二进制右移运算符。将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。 | A >> 2 将得到 15,即为 0000 1111 |
实例:
#include <stdio.h>
#include <stdlib.h>
int main()
{
unsigned int a = 60; /* 60 = 0011 1100 */
unsigned int b = 13; /* 13 = 0000 1101 */
int c = 0;
c = a & b; /* 12 = 0000 1100 */
printf("Line 1 - c 的值是 %d\n", c);
c = a | b; /* 61 = 0011 1101 */
printf("Line 2 - c 的值是 %d\n", c);
c = a ^ b; /* 49 = 0011 0001 */
printf("Line 3 - c 的值是 %d\n", c);
c = ~a; /*-61 = 1100 0011 */
printf("Line 4 - c 的值是 %d\n", c);
c = a << 2; /* 240 = 1111 0000 */
printf("Line 5 - c 的值是 %d\n", c);
c = a >> 2; /* 15 = 0000 1111 */
printf("Line 6 - c 的值是 %d\n", c);
system("pause");
return 0;
}
程序生成图:
对于移位运算符,不要移动负数位,这个是标准未定义的。 例如:
int num = 10;
num>>-1;//error
关系运算符
下表为 C 语言支持的所有关系运算符。假设变量 A 的值为 10,变量 B 的值为 20,则:
运算符 | 描述 | 实例 |
---|---|---|
== | 检查两个操作数的值是否相等,如果相等则条件为真。 | (A == B) 不为真。 |
!= | 检查两个操作数的值是否相等,如果不相等则条件为真。 | (A != B) 为真。 |
检查左操作数的值是否大于右操作数的值,如果是则条件为真。 | (A > B) 不为真。 | |
< | 检查左操作数的值是否小于右操作数的值,如果是则条件为真。 | (A < B) 为真。 |
>= | 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。 | (A >= B) 不为真。 |
<= | 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。 | (A <= B) 为真。 |
实例:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a = 10;
int b = 20;
if (a == b){
printf("Line 1 - a 等于 b\n");
}
else{
printf("Line 1 - a 不等于 b\n");
}
if (a < b){
printf("Line 2 - a 小于 b\n");
}
else{
printf("Line 2 - a 不小于 b\n");
}
if (a > b){
printf("Line 3 - a 大于 b\n");
}
else{
printf("Line 3 - a 不大于 b\n");
}
/* 改变 a 和 b 的值 */
a = a + b;
b = a - b;
a = a - b;
if (b <= a){
printf("Line 4 - b 小于或等于 a\n");
}
if (a >= b){
printf("Line 5 - a 大于或等于 b\n");
}
system("pause");
return 0;
}
程序生成图:
逻辑运算符
下表为 C 语言支持的所有关系逻辑运算符。假设变量 A 的值为 1,变量 B 的值为 0,则:
运算符 | 描述 | 实例 |
---|---|---|
&& | 称为逻辑与运算符。如果两个操作数都非零,则条件为真。 | (A && B) 为假。 |
|| | 称为逻辑或运算符。如果两个操作数中有任意一个非零,则条件为真。 | (A || B) 为真。 |
! | 称为逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假。 | !(A && B) 为真; !(A || B) 为假 |
||前者为true不执行后者,&&前者为false不执行后者
实例:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a = 1;
int b = 0;
if (a && b){
printf("Line 1 - 条件为真\n");
}
else{
printf("Line 1 - 条件不为真\n");
}
if (a || b){
printf("Line 2 - 条件为真\n");
}
else{
printf("Line 2 - 条件不为真\n");
}
if (!(a && b)){
printf("Line 3 - 条件为真\n");
}
else{
printf("Line 3 - 条件不为真\n");
}
if (!(a || b)){
printf("Line 3 - 条件为真\n");
}
else{
printf("Line 3 - 条件不为真\n");
}
system("pause");
return 0;
}
程序生成图:
赋值运算符
下表列出 C 语言支持的赋值运算符:
运算符 | 描述 | 实例 |
---|---|---|
= | 简单的赋值运算符,把右边操作数的值赋给左边操作数 | C = A + B 将把 A + B 的值赋给 C |
+= | 加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数 | C += A 相当于 C = C + A |
-= | 减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数 | C -= A 相当于 C = C - A |
*= | 乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数 | C *= A 相当于 C = C * A |
/= | 除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数 | C /= A 相当于 C = C / A |
%= | 求模且赋值运算符,求两个操作数的模赋值给左边操作数 | C %= A 相当于 C = C % A |
<<= | 左移且赋值运算符 | C <<= 2 等同于 C = C << 2 |
>>= | 右移且赋值运算符 | C >>= 2 等同于 C = C >> 2 |
&= | 按位与且赋值运算符 | C &= 2 等同于 C = C & 2 |
^= | 按位异或且赋值运算符 | C ^= 2 等同于 C = C ^ 2 |
|= | 按位或且赋值运算符 | C |= 2 等同于 C = C | 2 |
实例:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a = 10;
int c = 0;
c = a;
printf("Line 1 - = 运算符实例,c 的值 = %d\n", c);
c += a;
printf("Line 2 - += 运算符实例,c 的值 = %d\n", c);
c -= a;
printf("Line 3 - -= 运算符实例,c 的值 = %d\n", c);
c *= a;
printf("Line 4 - *= 运算符实例,c 的值 = %d\n", c);
c /= a;
printf("Line 5 - /= 运算符实例,c 的值 = %d\n", c);
c = 201;
c %= a;
printf("Line 6 - %= 运算符实例,c 的值 = %d\n", c);
c <<= 2;
printf("Line 7 - <<= 运算符实例,c 的值 = %d\n", c);
c >>= 2;
printf("Line 8 - >>= 运算符实例,c 的值 = %d\n", c);
c &= 2;
printf("Line 9 - &= 运算符实例,c 的值 = %d\n", c);
c ^= 2;
printf("Line 10 - ^= 运算符实例,c 的值 = %d\n", c);
c |= 2;
printf("Line 11 - |= 运算符实例,c 的值 = %d\n", c);
system("pause");
return 0;
}
程序生成图:
其他运算符 ↦ sizeof & 三目
下表列出了 C 语言支持的其他一些重要的运算符,包括 sizeof 和 ? :。
运算符 | 描述 | 实例 |
---|---|---|
sizeof() | 返回变量的大小。 | sizeof(a) 将返回 4,其中 a 是整数。 |
& | 返回变量的地址。 | &a; 将给出变量的实际地址。 |
* | 间接访问操作符 | *a; 将指向一个变量。 |
? : | 条件表达式 | 如果条件为真 ? 则值为 X : 否则值为 Y |
[] | 下标引用操作符 | 一个数组名 + 一个索引值 arr[9] = 10 |
() | 函数调用操作符 接受一个或者多个操作数 | 第一个操作数是函数名,剩余的操作数就是传递给函数的参数。rexp[rexp] |
. | 访问结构成员 | lexp.member_name |
-> | 访问结构指针成员 | rexp->member_name |
, | 逗号 | rexp,rexp |
#include <stdio.h>
#include <stdlib.h>
void test1()
{
printf("hehe\n");
}
int main()
{
int a = 4;
short b;
double c;
int* ptr;
/* sizeof 运算符实例 */
printf("Line 1 - 变量 a 的大小 = %lu\n", sizeof(a));
printf("Line 2 - 变量 b 的大小 = %lu\n", sizeof(b));
printf("Line 3 - 变量 c 的大小 = %lu\n", sizeof(c));
/* & 和 * 运算符实例 */
ptr = &a; /* 'ptr' 现在包含 'a' 的地址 */
printf("a 的值是 %d\n", a);
printf("*ptr 是 %d\n", *ptr);
/* 三目运算符实例 */
a = 10;
b = (a == 1) ? 20 : 30;
printf("b 的值是 %d\n", b);
b = (a == 10) ? 20 : 30;
printf("b 的值是 %d\n", b);
int arr[10];//创建数组
arr[9] = 10;//实用下标引用操作符。
printf("arr[9] 的值是 %d\n", arr[9]);
test1();
system("pause");
return 0;
}
程序生成图:
逗号表达式的说明:
表达式1,表达式2,表达式3,… ,表达式n
逗号表达式的要领:
- 逗号表达式的运算过程为:从左往右逐个计算表达式。
- 逗号表达式作为一个整体,它的值为最后一个表达式(也即表达式n)的值。
- 逗号运算符的优先级别在所有运算符中最低。
实例:
(3+5,6+8) 称为逗号表达式,其求解过程先表达式1,后表达式2,整个表达式值是表达式2的值 14
a=(a=35,a4)的值是60,其中(a=35,a4)的值是60, a的值在逗号表达式里一直是15,最后被逗号表达式赋值为60,a的值最终为 60
C 中的运算符优先级
运算符的优先级确定表达式中项的组合。这会影响到一个表达式如何计算。
指针 > 单目运算符 > 双目运算符(算术运算符 > 移位运算符 > 位运算符 > 逻辑运算符)> 三目运算符 > 赋值运算符 > 逗号运算符
下表将按运算符优先级从高到低列出各个运算符,具有较高优先级的运算符出现在表格的上面,具有较低优先级的运算符出现在表格的下面。在表达式中,较高优先级的运算符会优先被计算。
类别 | 运算符 | 结合性 |
---|---|---|
后缀 | () [] -> . ++ -- | 从左到右 |
一元 | + - ! ~ ++ -- (type)* & sizeof | 从右到左 |
乘除 | * / % | 从左到右 |
加减 | + - | 从左到右 |
移位 | << >> | 从左到右 |
关系 | < <= > >= | 从左到右 |
相等 | == != | 从左到右 |
位与 AND | & | 从左到右 |
位异或 XOR | 从左到右 | |
位或 OR | | | 从左到右 |
逻辑与 AND | && | 从左到右 |
逻辑或 OR | || | 从左到右 |
条件 | ?: | 从右到左 |
赋值 | = += -= *= /= %=>>= <<= &= ^= |= | 从右到左 |
逗号 | , | 从左到右 |
习题
- 输入参数为197时,函数返回多少?
int Function(unsigned int n) {
n = (n & 0x55555555) + ((n >> 1) & 0x55555555);
n = (n & 0x33333333) + ((n >> 2) & 0x33333333);
n = (n & 0x0f0f0f0f) + ((n >> 4) & 0x0f0f0f0f);
n = (n & 0x00ff00ff) + ((n >> 8) & 0x00ff00ff);
n = (n & 0x0000ffff) + ((n >> 16) & 0x0000ffff);
return n;
}
A. 2
B. 3
C. 4
D. 5
正确答案:
C
答案解析:
0x55555555:01010101 01010101 01010101 01010101
0x33333333:00110011 00110011 00110011 00110011
0x0f0f0f0f: 00001111 00001111 00001111 00001111
0x00ff00ff: 00000000 11111111 00000000 11111111
0x0000ffff: 00000000 00000000 11111111 11111111
这里运用了分治法计算二进制数中1的个数 ,这个算法叫做平行算法。
(n & 0x55555555) + ((n >> 1) & 0x55555555) 计算每对相邻的2位中有几个1
(n & 0x33333333) + ((n >> 2) & 0x33333333) 计算每相邻的4位中有几个1
接下来8位,16位,32位,对于32位的机器,5条位运算语句就够了。
其实很简单,先将n写成二进制形式,然后相邻位相加,重复这个过程,直到只剩下一位。
举例:
197: 11000101
- 1000 0101
- 0010 0010
- 0000 0100 (所以答案是4)
- In the main() function, after ModifyString(text) is called, what’s the value of ‘text’?
int FindSubString( char* pch )
{
int count = 0;
char * p1 = pch;
while ( *p1 != '\0' )
{
if ( *p1 == p1[1] - 1 )
{
p1++;
count++;
}else {
break;
}
}
int count2 = count;
while ( *p1 != '\0' )
{
if ( *p1 == p1[1] + 1 )
{
p1++;
count2--;
}else {
break;
}
}
if ( count2 == 0 )
return(count);
return(0);
}
void ModifyString( char* pText )
{
char * p1 = pText;
char * p2 = p1;
while ( *p1 != '\0' )
{
int count = FindSubString( p1 );
if ( count > 0 )
{
*p2++ = *p1;
sprintf( p2, "%i", count );
while ( *p2 != '\0' )
{
p2++;
}
p1 += count + count + 1;
}else {
*p2++ = *p1++;
}
}
}
void main( void )
{
char text[32] = "XYBCDCBABABA";
ModifyString( text );
printf( text );
}
A. XYBCDCBABABA
B. XYBCBCDA1BAA
C. XYBCDCBA1BAA
D. XYBCDDBA1BAB
正确答案
C
答案解析
*p1++ = *p2++
c语言从右往左压栈,
- 先计算p2++,产生对p2的一份拷贝,
- ++操作符增加p2的值(将指针p2向后移动1个自身长度的偏移量);
- 再计算*p2:对p2的拷贝上执行解引用;
- 再计算p1++,产生对p1的一份拷贝,
- ++操作符增加p1的值(将指针p1向后移动1个自身长度的偏移量);
- 再计算*p1:对p1的拷贝上执行解引用;
- 最后将第3步所得结果赋到第6步中的内存,即*p1=*p2;
简单理解: *p1=*p2; p1+1,p2+1;
FindSubString() 函数就是要找到一个先递增再递减且递增和递减的数量相等的回文序列,例如: ABCDCBA ,先是 后一项 = 前一项 ASCII 码 +1 , 后是 后一项 = 前一项 ASCII 码 -1 ,才能返回回文子串的长度,否则返回 0 。
ModifyString() 函数不断寻找上述类型的子串,如果不满足条件,就
*p2++ = *p1++;
当遇到 ABABA 中前一个 ABA 的时候,满足回文子串要求,此时 p1 指向 A BABA , p2 指向 ABABA ; sprintf 重定向修改 ABABA , B 变为 1 ,且跟随一个 ‘\0’ (该函数自动产生的) , 此时,字符串变为 A1‘\0’BA 。
经过 while ( *p2 != ‘\0’ ) 循环之后, p2 指向 A1‘\0’BA , p1 += count + count + 1 之后, p1 指向 A1‘\0’BA 。此时字符串已经被改动,之前的 ABABA 已经不存在,变为 A1‘\0’BA 。
再次进入 while ( *p1 != ‘\0’ ) 循环之后,只能执行 else 部分的命令, p1 指向 p2 指向的元素的后一个,不断将 p1 指向的元素传给 p2 所指向的位置,将原数据覆盖。所以, A1‘\0’BA ,依次变为 A1BBA 、 A1BAA 。即最终结果为 XYBCDCBA1BAA 。
int i=10,j=10,k=3;k*=i+j
k最后的值是?
A. 60
B. 40
C. 50
D. 23
正确答案:A
答案解析:
等价于下面的式子
int i=10,j=10,k=3;
k=k*(i+j);
- 若有定义语句:
int year=2009, int* p=&year ;
以下不能使变量year中的值增至2010的语句是()。
A. (*p)++;
B. *p++;
C. ++(*p);
D. *p+=1;
正确答案:
B
答案解析:
指针变量存储的是其指向的变量的地址,*p表示的是p指向的变量的值,自加运算符(++)的优先级要高于取值运算符( * )的优先级,*p++表示的是先将指针p指向下一个地址然后再取该地址的值,所以得到的结果并不是把year的值增至2010
- 下面程序的输出结果是______。
#include <iostream>
#define SQR(A) A*A
int main() {
int x=6,y=3,z=2;
x/=SQR(y+z)/SQR(y+z);
cout< < x< < endl;
}
A、0
B、6
C、5
D、1
正确答案: A
答案解析:
x/=SQR(y+z)/SQR(y+z);
等效于x= x / (SQR(y+z)/SQR(y+z));
所以 x = 6 / (3 + 2 * 3 + 2 / 3 + 2 * 3 + 2)
如有不同见解,欢迎留言讨论~~
上一篇: 4大类运算符
下一篇: 理解C语言中的逻辑运算符和关系运算符
推荐阅读
-
深入理解C++标准库(第2讲):OOP GP、内存管理、容器间的关系及Vector的深度探讨(视频16)
-
深入理解C/C++中的静态变量使用方法
-
理解JavaScript中的运算符优先级规则
-
深入理解C/C++中的运算符及它们的优先级规则
-
深入理解运算符的优先级规则
-
C++中的运算符重载:理解内部和外部的实现方式
-
C++中的运算符优先级详解
-
理解Java中的运算符及其优先级规则
-
C++中的运算符重载:理解并应用 bool 类型的重载
-
【2022新手指南】Java编程进阶之路 - 六、技术架构篇 ### MySQL索引底层解析与优化实战 - 你会讲解MySQL索引的数据结构吗?性能调优技巧知多少? - Redis深度揭秘:你知道多少?从基础到哨兵、主从复制全梳理 - Redis持久化及哨兵模式详解,还有集群搭建和Leader选举黑箱打开 - Zookeeper是个啥?特性和应用场景大公开 - ZooKeeper集群搭建攻略及 Leader选举、读写一致性、共享锁实现细节 - 探究ZooKeeper中的Leader选举机制及其在分布式环境中的作用 - Zab协议深入剖析:原理、功能与在Zookeeper中的核心地位 - RabbitMQ全方位解读:工作模式、消费限流、可靠投递与配置策略 - 设计者视角:RabbitMQ过期时间、死信队列与延时队列实践指南 - RocketMQ特性和应用场景揭示:理解其精髓与差异化优势 - Kafka详细介绍:特性及广泛应用于实时数据处理的场景解析 - ElasticSearch实力揭秘:特性概述与作为搜索引擎的广泛应用 - MongoDB认知升级:非关系型数据库的优势阐述,安装与使用实战教学 - BIO/NIO/AIO网络模型对比:掌握它们的区别与在网络编程中的实际应用 - Netty带你飞:理解其超快速度背后的秘密,包括线程模型分析 - 网络通信黑科技:Netty编解码原理与常用编解码器的应用,Protostuff实战演示 - 解密Netty粘包与拆包现象,怎样有效应对这一常见问题 - 自定义Netty心跳检测机制,轻松调整检测间隔时间的艺术 - Dubbo轻骑兵介绍:核心特性概览,服务降级实战与其实现益处 - Dubbo三大神器解读:本地存根与本地伪装的实战运用与优势呈现 ----------------------- 七、结语与回顾