操作符详解与表达式求值

目录

操作符分类

 1.算数操作符

2.移位操作符(只适用于整数范围)

(1)引入

(2)左移操作符<<

(2)右移操作符>>

 3.位操作符

 4.赋值操作符

 复合赋值符

5.单目操作符

5.1单目操作符介绍

5.2单目操作符的一些常见场景

5.3sizeof和数组练习题

 6.关系操作符

 7.逻辑操作符

8.条件操作符

 9.逗号表达式

10.下标引用、函数调用、结构成员

10.1下标引用操作符[ ],操作数:一个数组名+一个索引值

10.2函数调用操作符()

10.3成员访问操作符 

​编辑 11.表达式求值

11.1隐式类型转换

 11.2算术转换

 11.3操作符属性


操作符分类

 1.算数操作符

        算术操作符主要有:+(加)、-(减)、*(乘)、/(除)、%(取余)。

        值得注意的几点有:(1)+、-、*、/都可用于整数与浮点数的运算,%又称整数取余,只适用于整数的运算,且返回的是整除之后的余数(求余数的操作符)。

                                        (2)当 +、-、*、/这几个操作数在处理整数与浮点数的混合运算时,运算结果为浮点类型。

        7 / 3的本质是整数/整数,发生的是整数除法,所以会先产生一个整数2,然后才会将2赋值给m,n。 

        7 / 3.0的本质是整数/浮点数,它会先产生一个浮点数2.333333,然后赋值给整型变量m的时候把小数部分截断了,但是浮点数变量n是能直接接收的。 

2.移位操作符(只适用于整数范围)

(1)引入

        整数的二进制表示形式有:原码、反码、补码。对于一个整型变量是4字节 = 32bit位。

        原码:按照数值的正负,直接写出的二进制序列(整型变量就是32个bit位)就是原码。

        对于有符号整数来说,第一位(最高位)是符号位,符号位为1表示是负数,符号位为0表示正数。 

        对于无符号整数来说,没有符号位,所有位都是有效位。

eg:10

原码->00000000 00000000 00000000 00001010从最低位到最高位(从右到左),第n位的位权为2^(n-1),但不包括符号位。

eg:-10

原码->10000000 00000000 00000000 00001010

        对于正的整数来说,原码反码补码三者一致,而对于负的整数来说,反码是原码的符号位不变,其他位按位取反;补码是反码+1。在内存中存一个整数的时候,存的是它的补码二进制序列,同时,在计算的时候,也是用补码来计算。

(2)左移操作符<<

        7的二进制序列是00000000 00000000 00000000 00000111,移位之后变成00000000 00000000 00000000 00001110,最高位要丢弃,最低位后面补0,也就是14,然后赋值给m。注意:n是不变的,还是7,它只是参与运算了。向左移动一位的时候和乘以2的效果是一样的。

        n = -7->

原码:10000000 00000000 00000000 00000111

反码:11111111 11111111 11111111 11111000

补码:11111111 11111111 11111111 11111001(计算机拿来计算)

左移:11111111 11111111 11111111 11110010(还是补码)

补码求反码  - 1:11111111 11111111 11111111 11110001

反码求原码:10000000 00000000 00000000 00001110(展示)

        左移的特点就是左边丢弃,右边补0。

(2)右移操作符>>

        右移操作符分为两种:一种右移叫算术右移,另一种叫逻辑右移。

        绝大多数的编译器采用的都是算术右移,但具体得根据编译器来判断。算术右移有除2的效果。

        对于移位操作符,不要移动负数位,这个是标准未定义的。eg:10 >> -1//error

 3.位操作符

         位操作符有:

&        按位与

|        按位或

^        按位异或

注:他们的操作数必须是整数。

eg:

int a = 3;

int b = -5

int c = a & b;

a的补码:00000000 00000000 00000000 00000011

b的原码:10000000 00000000 00000000 00000101

b的反码:11111111 11111111 11111111 11111010

b的补码:11111111 11111111 11111111 11111011

计算机计算过程拿补码来进行计算

所以a & b ->

00000000 00000000 00000000 00000011

11111111 11111111 11111111 11111011

=>00000000 00000000 00000000 00000011

因为符号位为0->正数,所以原码也是这个即3

        按位或和按位异或都是这样计算的。就不举例计算了,需要知道在位操作上,1或上任何数=1,0或上任何数=任何数 异或就是相同为0,不同为1。异或有两条常用的数学性质:a ^ a = 0;0 ^ x = x。

         不创建临时变量(第三个变量),实现两个数的交换

        这是创建临时变量的做法。 可以用加减来实现不创建临时变量的做法->

        这种方法会有溢出的风险,因为a + b可能超出int的范围 。

        这种用位运算(异或操作)来实现两个数的交换,且不会有溢出风险。 注:异或操作是支持交换律的。但是这种方法只适用于整型,浮点数不支持异或操作。

练习:求一个整数存储在内存中的二进制中1的个数->

        这个题的意思就是找出这个数在内存中的二进制序列中1的个数,可以使用按位与&1操作这个二进制序列上的每一位,如果是1,计数器++->

#include <stdio.h>
int main()
{int num = 10;int count = 0;//统计1的个数for (int i = 0; i < 32; i++){num = num >> i;if ((num & 1) == 1)count++;}printf("%d\n", count);return 0;
}

        当然也可以让1去左移31位去&num,判断num的二进制序列上的每一位是不是1,其实还有一种思路,就是在上述基础上判断那里可以改成num是否是奇数,因为最低位是1,表示它是个奇数,其他位的位权全是2的倍数。->

#include <stdio.h>
int main()
{int num = 10;int count = 0;//统计1的个数while (num){if (num % 2 == 1)count++;num = num / 2;}printf("%d\n", count);return 0;
}

        注:一定是/2,因为当num在内存中的二进制序列表示负数的时候,符号位也要算上去,如果是>>此时,编译器默认为是算术右移,会一直将符号位置1,导致陷入死循环->

        总之第二种方法的在适用范围很小,很容易出错,慎用。 

 4.赋值操作符

#include <stdio.h>int main()
{int a = 20;//不是赋值,初始化a = 30;//赋值return 0;
}

        赋值是变量存在的情况下,修改变量的值,赋值是能够连续赋值的->

int a = 30;
int x = 20;
int y = 10;
a = x = y * 5;

        在连续赋值那块,是从右向左依次赋值,等效于下面->

x = y * 5;
a = x;

        下面这种操作,逻辑较清晰,且易于调试。

 复合赋值符

+=-=*=/=%=
>>=<<=&=|=^=

         这些运算符都可以拆分为 x = x () y;就是x和y运算后的结果存入x。

5.单目操作符

5.1单目操作符介绍

!                                逻辑反操作

-                                负值

+                               正值

&                               取地址

sizeof                        操作数的类型长度(以字节为单位)

~                               对一个数的二进制按位取反

--                               前置--,后置--

++                             前置++,后置++

*                                间接访问操作符(解引用操作符)

(类型)                        强制类型转换

        单目操作符的意思就是只有一个操作数。 

5.2单目操作符的一些常见场景

#include <stdio.h>int main()
{int flag = 0;int x = !flag;if (!flag){printf("%d\n", x);printf("%d\n", !100);}return 0;
}

         !就是将逻辑真(非0),变为逻辑假(0),逻辑假变为逻辑真。

#include <stdio.h>int main()
{int a = 50;int b = +a;printf("%d\n", b);int c = -a;printf("%d\n", c);return 0;
}

         正值+操作符本质上是没什么意义的,并不会改变操作数的值。

#include <stdio.h>int main()
{int a = 20;int* p = &a;int arr[20];int (*pa)[10] = &arr;return 0;
}

        注:&arr是数组的地址,我们需要用数组指针来接收它的地址,比如整型变量a的地址用整型指针来接收a的地址,这里关于指针、地址后面会细讲,这里不必太关心细节。 

#include <stdio.h>int main()
{int a = 10;int* p = &a;printf("%d\n", *p);return 0;
}

        *p的意思就是对p进行解引用,也就是通过p存放的地址,找到p指向的对象->例如变量a在内存中的地址是0x3f3f3f3f,这个地址存的数据就是a的值10,然后用指针变量p(p也是个变量,也有自己的地址)来存储变量a的地址0x3f3f3f3f,那我*p就是找到这块地址对应的值,也就是a。*p和a是同一块空间,p是另一块空间,这块空间存的是a那块空间的地址。 

        但其实在新版的编译器下,sizeof的返回值类型是size_t(无符号整型),对此类型的数据进行打印,可以使用%zd->

        老版本的编译器没有size_t类型,所以没有%zd的打印格式,可以使用%d或者%u进行打印。sizeof a(这一点也可以说明sizeof是操作符而不是函数,因为函数调用需要函数调用操作符())和sizeof(a)都是可以的,但对于数据类型应该是sizeof(数据类型)。 

        sizeof 数组名是计算整个数组的大小,因为数组元素是int类型的,一个元素占4字节,那十个元素就是占四十字节。sizeof是在计算类型创建变量或者变量的大小,单位是字节。

 

#include <stdio.h>int main()
{int a = 0;printf("%d\n", ~a);return 0;
}

        记得转换为二进制序列再操作,这是计算机计算的规则,换算回来就是-1。 

        那我们如何再换算回来呢?->

         ++自增操作符和--自减操作符->

        这种++在a的前面称为前置++,在后面称为后置++;在前面还是在后面对操作数a是没有影响的,都是使其+1,但对于“别人”是有影响的->

  

        前置++的计算口诀是先让操作数a+1,再使用操作数a;后置++的计算口诀是先使用操作数a,再让操作数a+1。++推广到--是类似的。

#include <stdio.h>int main()
{int a = 1;int b = (++a) + (++a) + (++a);printf("%d\n", b);return 0;
}

        这种代码是无意义的,在不同编译器、不同平台、不同环境,算出来的结果是不一样的。工程中应当尽量减少这种未定义行为!在vs2022下运行的结果是12,但是我换一个gcc环境->

        这里的输出答案是10。同一段代码,在gcc环境和vs下的输出结果都不一样,编译器的输出都不一样,那人还怎么算?

        我们知道这样初始化,编译器会将3.14识别为double类型,但a是个整型类型,所以编译器会弹出警告,要消除这个警告,可以用double的类型变量来接受这个3.14,或者是将3.14进行强制类型转换->

        强制类型转换是到万不得已的情况才使用,毕竟“强扭的瓜不甜”。

5.3sizeof和数组练习题

#include <stdio.h>
void test1(int arr[])
{printf("%d\n", sizeof(arr));//(2)
}
void test2(char ch[])
{printf("%d\n", sizeof(ch));//(4)
}
int main()
{int arr[10] = {0};char ch[10] = {0};printf("%d\n", sizeof(arr));//(1)printf("%d\n", sizeof(ch));//(3)test1(arr);test2(ch);return 0;
}
问:
(1)、(2)两个地方分别输出多少?
(3)、(4)两个地方分别输出多少?

        (1)、(3) 很明显是40和10,那(2)和(4)呢?

         我这边是x64环境,输出的是8和8,因为数组传参的时候,传的是数组首元素的地址,是个指针类型的变量,即使形参写成那个样子,但那并不是本质,只是语法允许这样写,所以传过去的是个指针,当然在x64环境下就是8字节,在x86环境下就是4字节(这里就不验证了)。

        只是语法上形参允许这样写,但底层是指针做形参,并不是用数组做形参,因为当数组很大的时候传参,形参是实参的一份拷贝,那这个效率就太低了,C语言是一门追求效率的语言,传地址过去,一样能找到那块空间。 

 6.关系操作符

        关系操作符唯一需要注意的就是不要把赋值操作符=和关系操作符==搞混了。

>

>=

<

<=

!=                 测试不等于

==                测试相等

 7.逻辑操作符

&&        逻辑与        并且

||           逻辑或        或者

 

 

#include <stdio.h>int main()
{int month = 0;scanf("%d", &month);if (month >= 3 && month <= 5)printf("春季\n");else if (month == 12 || month == 1 || month == 2)printf("冬季\n");return 0;
}

 360笔试题:

#include <stdio.h>
int main()
{int i = 0,a=0,b=2,c =3,d=4;i = a++ && ++b && d++;//i = a++ || ++b || d++;printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);return 0;
}
//程序输出的结果是什么?

         i = a++ && ...我们知道a++表达式的值是0,然后a += 1,->i = 0 && ...我们知道0 && ...等于0,后面的表达式的值已经不重要了,因此这个逻辑表达式&&会发生逻辑短路,后面表达式的值不会再算下去,直接输出i = 0。这种逻辑运算符的这种特性叫做逻辑短路

        我们现在换成||逻辑运算符->

        i = a++ || ++b || d++,a++表达式的值是0,然后a += 1;->i = 0 || ++b || ...,因为0 || ...的结果还可能是真,所以后面会继续计算,++b表达式的值是3,b也已经变成了3,0 || 3 -> 1所以变成1 || ...,此时不管后面的表达式是真还是假,整体一定是真,||左边遇1则整体为1(逻辑真),所以后面的表达式已经没有必要算下去了,即发生了逻辑短路。

        所以,&&左边遇0(假)即发生逻辑短路,||左边遇真(非0)也会发生逻辑短路。或许我们可以利用这个特性,将优先级高的放左边,优先级低的放右边,然后就会发生一些不可思议的现象。

8.条件操作符

        exp1 ? exp2 : exp3   其中expi是表达式,条件操作符是唯一的一个三目操作符。如果表达式1exp1的结果为真,那么就计算表达式2,不计算表达式3,表达式2的结果就是整个表达式的结果;如果表达式1exp1的结果为假,那就计算表达式3,不计算表达式2,表达式3的结果就是整个表达式的结果。

         eg:计算a和b的较大值->

        转换成条件表达式-> 

        这两种写法是等效的。 

 9.逗号表达式

        exp1,exp2,exp3,...expn。逗号表达式就是用逗号隔开的表达式。逗号表达式从,从左至右依次进行。整个表达式的结果是最后一个表达式的结果。

int a = 1;
int b = 2;
int c = (a>b, a=b+10, a, b=a+1);
c是多少?

        从左至右依次计算过去,最后一个表达式的结果为整个表达式的结果。

a = get_val();
count_val(a);
while (a > 0)
{//业务处理a = get_val();count_val(a);
}

         因为上面和循环内部是重复的,所以可以利用逗号表达式的特点写成->

while (a = get_val(), count_val(a), a>0)
{//业务处理
}

10.下标引用、函数调用、结构成员

10.1下标引用操作符[ ],操作数:一个数组名+一个索引值

int arr[10];//创建数组
arr[9] = 10;//实用下标引用操作符。
[ ]的两个操作数是arr和9。

        数组的下标也叫数组元素的索引。

10.2函数调用操作符()

 #include <stdio.h>void test1(){printf("hehe\n");}void test2(const char *str){printf("%s\n", str);}int main(){test1();            //实用()作为函数调用操作符。test2("hello bit.");//实用()作为函数调用操作符。return 0;}

        对于函数调用操作符来说,有两个操作数,一个是函数名,另一个是参数。对于一个函数调用操作符来说,它至少要有一个操作数——函数名。

10.3成员访问操作符 

. 结构体                    .成员名
-> 结构体指针        ->成员名

#include <stdio.h>struct Book
{char name[20];int price;
};int main()
{struct Book book1 = { "C语言学习", 100 };printf("%s %d\n", book1.name, book1.price);return 0;
}

         *pb就是book1这块空间,再.访问变量的成员,C语言语法支持直接pb->成员。二者是完全等价的。

 11.表达式求值

        表达式求值的顺序一部分是右操作符的优先级和结合性决定。同样,有些表达式的操作数在求值过程中可能需要转换成其他类型。

11.1隐式类型转换

         C语言的整型算术运算总是至少以缺省整型类型的精度来进行的。为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。

1.整形提升

2.算术转换

        整型提升的意义:

表达式的整型运算要在 CPU 的相应运算器件内执行, CPU 内整型运算器 (ALU) 的操作数的字节长度一般就是int 的字节长度,同时也是 CPU 的通用寄存器的长度。
因此,即使两个 char 类型的相加,在 CPU 执行时实际上也要先转换为 CPU 内整型操作数的标准长度。
通用 CPU general-purpose CPU )是难以直接实现两个 8 比特字节直接相加运算(虽然机器指令 中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int 长度的整型值,都必须先转换为int unsigned int ,然后才能送入 CPU 去执行运算。

int main()
{char a = 5;//5是整型://00000000 00000000 00000000 00000101//a只能接受8位//00000101char b = 126;//126是整型://00000000 00000000 00000000 01111110//b只能接受8位//01111110char c = a + b;//a、b发生整型提升//a:00000000 00000000 00000000 00000101//b:00000000 00000000 00000000 01111110//相加:00000000 00000000 00000000 10000011//c:10000011//打印的时候,又整型提升:11111111 11111111 11111111 10000011//补码转换为原码打印:10000000 00000000 00000000 01111101	——-125printf("%d\n", c);return 0;
}

        b和c的值被提升为普通整型,然后在执行加法运算。加法运算完成后,结果被截断,然后在存储在a中。int——signed int(默认)、char到底是signed char还是unsigned char是不确定的,C语言标准没有明确规定,是取决于编译器的,在当前使用的vs2022下,char默认为signed char。整型提升的时候,如果是有符号类型,记得在高位全部加0/1(原先的符号位是0就加0是1就加1)。CPU就是这样算的,但是我们直接5和126的补码直接开算也是对的。

//实例1
#include <stdio.h>int main()
{char a = 0xb6;short b = 0xb600;int c = 0xb6000000;if(a==0xb6)printf("a");if(b==0xb600)printf("b");if(c==0xb6000000)printf("c");return 0;
}

        在判断a==0xb6的时候,a会发生整型提升,由于符号位是1,所以高位全部补1,可以想简单点就是,0xb6赋值给a的时候,a是个负数,0xb6是实打实的正数(整型),同理b也是如此。

//实例2
#include <stdio.h>int main()
{char c = 1;printf("%u\n", sizeof(c));printf("%u\n", sizeof(+c));printf("%u\n", sizeof(-c));return 0;
}

        实例2 中的 ,c 只要参与表达式运算 , 就会发生整形提升 , 表达式 +c , 就会发生提升 , 所以 sizeof(+c) 4 个字节。表达式 - c 也会发生整形提升 , 所以 sizeof( - c) 4 个字节 , 但是 sizeof(c) , 就是 1 个字节。

 11.2算术转换

        如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系转换称为寻常算术转换。(小范围->大范围)

long double

double

float

unsigned long int

long int

unsigned int

int

        如果某个操作数的类型在上面这个表格中排名较低,那么首先要转换为另一个操作数的类型后执行运算。注意: 数据类型在整型大小以下,才会发生整型提升,在整型大小及以上才会发生算术转换,且转换要合理,要不然会存在一些潜在问题。

float f = 3.14;
int num = f;//隐式转换,会有精度丢失

 11.3操作符属性

        复杂表达式的求值有三个影响的因素——>

1.操作符的优先级

2.操作符的结合性

3.是否控制求值顺序

        两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。控制求值顺序比如逻辑操作符的逻辑短路和条件操作符的取其一求值。

操作符优先级:

操作
描述
用法示例
结果类
结合
是否控制求值
顺序
()
聚组
(表达式)
与表达
式同
N/A
()
函数调用
rexp rexp ...,rexp
rexp
L-R
[ ]
下标引用
rexp[rexp]
lexp
L-R
.
访问结构成员
lexp.member_name
lexp
L-R
->
访问结构指针成员
rexp->member_name
lexp
L-R
++
后缀自增
lexp ++
rexp
L-R
--
后缀自减
lexp --
rexp
L-R
!
逻辑反
! rexp
rexp
R-L
~
按位取反
~ rexp
rexp
R-L
+
单目,表示正值
+ rexp
rexp
R-L
-
单目,表示负值
- rexp
rexp
R-L
++
前缀自增
++ lexp
rexp
R-L
--
前缀自减
-- lexp
rexp
R-L
*间接访问
* rexp
lexp
R-L
&
取地址
& lexprexp
R-L
sizeof
取其长度,以字节
表示
sizeof rexp sizeof(类型)
rexp
R-L
(
型)
类型转换
( 类型 ) rexp
rexp
R-L
*
乘法
rexp * rexp
rexp
R-L
/
除法
rexp / rexp
rexp
L-R
%
整数取余
rexp % rexp
rexp
L-R
+
加法
rexp + rexp
rexp
L-R
-
减法
rexp - rexp
rexp
L-R
<<
左移位
rexp << rexp
rexp
L-R
>>
右移位
rexp >> rexp
rexp
L-R
>
大于
rexp > rexp
rexp
L-R
>=
大于等于
rexp >= rexp
rexp
L-R
<
小于
rexp < rexp
rexp
L-R
<=
小于等于
rexp <= rexp
rexp
L-R
==
等于
rexp == rexp
rexp
L-R
!=
不等于
rexp != rexp
rexp
L-R
&
位与
rexp & rexp
rexp
L-R
^
位异或
rexp ^ rexp
rexp
L-R
|
位或
rexp | rexp
rexp
L-R
&&
逻辑与
rexp && rexp
rexp
L-R
||
逻辑或
rexp || rexp
rexp
L-R
? :
条件操作符
rexp ? rexp : rexp
rexp
N/A
=
赋值
lexp = rexp
rexp
R-L
+=
...
lexp += rexp
rexp
R-L
-=
...
lexp -= rexp
rexp
R-L
*=
...
lexp *= rexp
rexp
R-L
/=
...
lexp /= rexp
rexp
R-L
%=
... 取模
lexp %= rexp
rexp
R-L
<<=
... 左移
lexp <<= rexp
rexp
R-L
>>=
... 右移
lexp >>= rexp
rexp
R-L
&=
...
lexp &= rexp
rexp
R-L
^=
... 异或
lexp ^= rexp
rexp
R-L
|=
...
lexp |= rexp
rexp
R-L
逗号
rexp rexp
rexp
L-R
int main()
{//优先级:相邻操作符int a = 10 + 5 * 2;//结合性:优先级相同情况下,结合性才有作用int b = 3 + 5 + 7;//先3 + 5还是先5 + 7呢?+的结合性是L-R就是先算3+5,从左到右计算。return 0;
}
//表达式的求值部分由操作符的优先级决定。
//表达式1
a*b + c*d + e*f
注释:代码 1 在计算的时候,由于 * + 的优先级高,只能保证, * 的计算是比 + 早,但是优先级并不
能决定第三个 * 比第一个 + 早执行。

        所以表达式的计算顺序可能是:

a * b
c * d
a * b + c * d
e * f
a * b + c * d + e * f
或者
a * b
c * d
e * f
a * b + c * d
a * b + c * d + e * f

        如果abcdef是变量不论哪种计算路径答案都是对的,但如果是表达式呢?(可能用到相同的变量)。 

//表达式2
c + --c;
注释:同上,操作符的优先级只能决定自减 -- 的运算在 + 的运算的前面,但是我们并没有办法得知,+ 操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的。

         +两边的操作数谁先准备呢?假设c=2,左边先准备,那就是2 + 1,右边先准备就是,--c,c变成1,在准备左边的c(已经变成了1),那就是1 + 1。

//代码3-非法表达式
int main()
{int i = 10;i = i-- - --i * ( i = -3 ) * i++ + ++i;printf("i = %d\n", i);return 0;
}

        表达式3在不同的编译器中测试结果:非法表达式程序的结果

        所以在实践中不要写这种未定义行为的代码,连不同的编译器输出结果都不一样,那还怎么搞。

//代码4
int fun()
{static int count = 1;return ++count;
}
int main()
{int answer;answer = fun() - fun() * fun();printf( "%d\n", answer);//输出多少?return 0;
}

        这段代码也有问题——虽然在大多数编译器上求得的结果都是相同的。但是上述代码answer = fun() - fun() * fun();我们只能通过操作符的优先级得知:先算乘法,再算减法。但函数调用先后顺序无法通过操作符的优先级确定。有两种可能:1. 2 - 3 * 4;2. 4 - 2 * 3。 vs2022下结果如下:

//代码5
#include <stdio.h>
int main()
{int i = 1;int ret = (++i) + (++i) + (++i);printf("%d\n", ret);printf("%d\n", i);return 0;
}

         可以试试看一下汇编代码,看看到底发生了啥,就可以分析清楚了->

        记得先切到x86环境下,这个环境下较简单一些。

         F11进入调试。记得调出右边的监视窗口。

        然后对着代码鼠标右键跳转到反汇编->

        记得关闭显示符号名->

         逐步调试下来发现->

        这段代码中的第一个 + 在执行的时候,第三个 ++ 是否执行,这个是不确定的,因为依靠操作符的优先级和结合性是无法决定第一个 + 和第三个前置 ++ 的先后顺序。

        vs2022下它的计算逻辑是先算三个++i再计算两次+即4 + 4 + 4 = 12。 

        总结:我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那么这个表达式就是存在问题的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1553107.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

深度优先搜索(DFS)与有向图中的唯一结点

深度优先搜索(DFS)与有向图中的唯一结点 前提与定义分析与方法伪代码与 C 代码实现解释结果在图论中,深度优先搜索(DFS)是一种用于遍历或搜索图的算法。DFS 从给定的起始结点出发,沿着图的深度方向尽可能深地搜索,直到无法继续为止,然后回溯并从未访问过的邻接结点继续…

Unraid的cache使用btrfs或zfs?

Unraid的cache使用btrfs或zfs&#xff1f; 背景&#xff1a;由于在unraid中添加了多个docker和虚拟机&#xff0c;因此会一直访问硬盘。然而&#xff0c;单个硬盘实在难以让人放心。在阵列盘中&#xff0c;可以通过添加校验盘进行数据保护&#xff0c;在cache中无法使用xfs格式…

YOLOv11改进 | Neck篇 | YOLOv11引入Slim-Neck(轻量)

1. Slim-Neck介绍 摘要&#xff1a;目标检测是计算机视觉中重要的下游任务。 对于车载边缘计算平台来说&#xff0c;巨大的模型很难达到实时检测的要求。 而且&#xff0c;由大量深度可分离卷积层构建的轻量级模型无法达到足够的精度。 我们引入了一种新的轻量级卷积技术 GSCon…

【顺序查找】

目录 一. 顺序查找的概念二. 查找的性能计算 \quad 一. 顺序查找的概念 \quad \quad 二. 查找的性能计算 \quad

使用ROCm的GPU感知MPI

GPU-aware MPI with ROCm — ROCm Blogs (amd.com) 注意: 此博客之前是 AMD Lab Notes博客系列的一部分。 MPI&#xff08;消息传递接口&#xff09;是高性能计算中进程间通信的事实标准。MPI进程在其本地数据上进行计算&#xff0c;同时进行大量的相互通信。这使得MPI程序可以…

【折半查找】

目录 一. 折半查找的概念二. 折半查找的过程三. 折半查找的代码实现四. 折半查找的性能分析 \quad 一. 折半查找的概念 \quad 必须有序 \quad 二. 折半查找的过程 \quad \quad 三. 折半查找的代码实现 \quad 背下来 \quad 四. 折半查找的性能分析 \quad 记住 比较的是层数 …

sed引入变量中的坑

sed引入变量问题 1、sed引入变量2、sed引入变量问题 1、sed引入变量 sed指令引入变量&#xff0c;直接使用双引号即可 例如&#xff0c;下面的示例&#xff1a; ab; echo "abc" | sed "s/b/$a/g"2、sed引入变量问题 但是&#xff0c;如果变量值中带有/等…

自闭症寄宿学校:释放孩子内心的美

在自闭症儿童的成长旅程中&#xff0c;寻找一个既能提供专业康复服务&#xff0c;又能让孩子感受到爱与关怀的教育环境&#xff0c;是许多家庭梦寐以求的目标。在广州&#xff0c;星贝育园自闭症儿童寄宿制学校正是这样一所充满爱与希望的学校&#xff0c;它不仅为自闭症儿童提…

CMU 10423 Generative AI:lec13/13.5(text-to-image models:三大类方法、评估标准、图像编辑原理)

1 文章目录 1 lec13和lec13.5概述2 Text-to-Image Generation 概念、主要方法、挑战、发展历程1. **基本概念**2. **主要技术方法**2.1. **生成对抗网络&#xff08;GAN&#xff09;**2.2. **自回归模型&#xff08;Autoregressive Models&#xff09;**2.3. **扩散模型&#x…

9.28学习笔记

1.ping 网址 2.ssh nscc/l20 3.crtl,打开vscode的setting 4.win 10修改ssh配置文件及其密钥权限为600 - 晴云孤魂 - 博客园 整体来看&#xff1a; 使用transformer作为其主干网络&#xff0c;代替了原先的UNet 在latent space进行训练&#xff0c;通过transformer处理潜…

Java项目实战II基于Java+Spring Boot+MySQL的智能物流管理系统(源码+数据库+文档)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者 一、前言 随着电商行业的蓬勃发展&#xff0c;物流行业迎来了前所未有的机遇与挑战。面对日益增长的订单量和复…

python如何显示数组

np.set_printoptions方法的相关属性&#xff1a; <span style"background-color:#272822"><span style"color:#f8f8d4">set_printoptions(precisionNone, thresholdNone, edgeitemsNone, linewidthNone, suppressNone, nanstrNone, infstrNo…

记一次RCE漏洞的利用

某微商代理商补货商城系统存在RCE漏洞 微商分销代理商城&#xff0c;可以自己设置代理等级和升级条件(如购买指定商品、消费额度)&#xff0c;“微商城小程序三级分销拼团秒杀多商户开店O2O门店”通过社交关系分销裂变&#xff0c;把粉丝变成客户&#xff0c;让分销商发展下线…

MDM监管锁系统ABM证书与MDM证书申请与使用

MDM证书与ABM证书申请与维护 基础知识 监管锁系统运行需要两个证书 分别为ABM证书 与 MDM证书,在别人平台购买的监管锁只会让你上传自己的ABM证书而MDM证书则是共用一个平台自己的MDM证书&#xff0c;而MDM证书才是控制手机的关键,如果MDM证书被封禁,那么所有的设备将无法受到…

MDM监管锁系统上锁流程

上锁与解锁 上锁设备 完整的上锁流程可参考: https://b23.tv/UvM35sU 上锁需要已经注册了一个普通用户 并使用管理员分配了台数 且有可用的MDM证书和ABM证书(公有和私有的都可以 只要有可用的就可以) 一部用来上锁的手机 链接wifi wifi必须要是2.4g频段 不要使用5gwifi 上锁…

PYTHON实现HTTP request的一些有用的函数

前言 我们知道&#xff0c;当需要设计一个程序和服务器进行交互时&#xff0c;往往会用到HTTP的request&#xff0c;即服务器有一个对外接口REST API&#xff0c;因此当向服务器发送符合格式要求的HTTP request时&#xff0c;服务器会给出响应&#xff0c;甚至执行一些任务。如…

睢宁自闭症寄宿学校:培养特殊孩子的未来

在自闭症儿童的教育与康复领域&#xff0c;每一所学校的努力都是对孩子们未来无限可能的一次深刻诠释。从江苏睢宁到广东广州&#xff0c;自闭症寄宿学校正以不同的方式&#xff0c;为这些特殊的孩子铺设一条通往未来的希望之路。其中&#xff0c;广州的星贝育园自闭症儿童寄宿…

【算法篇】回溯算法类(1)(笔记)

目录 一、理论基础 1. 相关题目 2. 遍历过程 3. 代码框架 二、LeetCode 题目 1. 组合 2. 组合总和III 3. 电话号码的字母组合 4. 组合总和 5. 组合总和II 6. 分割回文串 7. 复原IP地址 8. 子集 一、理论基础 1. 相关题目 2. 遍历过程 3. 代码框架 void backtr…

SSM环卫人员管理平台—计算机毕业设计源码36412

目 录 摘要 1 绪论 1.1背景及意义 1.2国内外研究概况 1.3研究内容 1.4 ssm框架介绍 1.5论文结构与章节安排 2 环卫人员管理平台系统分析 2.1 可行性分析 2.2 系统流程分析 2.2.1数据增加流程 2.2.2数据修改流程 2.2.3数据删除流程 2.3 系统功能分析 2.3.1 功能性…

JAVAIDEA初始工程的创建

四结构 建工程综述* 初始*&#xff1a; 1、先建个空项目&#xff0c; 2、打开文件中的项目结构新建module模块&#xff08;模块下有src&#xff09; 修改模块名&#xff1a; 也是Refactor&#xff0c;Rename&#xff0c;但是要选第三个同时改模块和文件夹名字 导入模块&am…