习题演练——操作符篇
前言
大家好,很高兴又和大家见面啦!!!经过前面三个篇章的介绍,我相信大家对操作符的知识以及很熟悉了,接下来我们就要开始趁热打铁,来上几道习题练练手,帮助大家巩固这个章节的内容。今天博主给大家带来了3道题目供各位朋友练手,题目如下:
不能创建临时变量(第三个变量),实现两个数的交换 编写代码实现:求一个整数存储在内存中的二进制中1的个数 360笔试题——程序输出的结果是什么?
下面我们就来开始进行今天的习题讲解吧;
1.不能创建临时变量(第三个变量),实现两个数的交换。
对于实现两个数的交换这道题,我们已经并不陌生了,可能有些朋友没有看过我之前的博客内容,所以今天我们还是一步一步的来讲解如何实现两个数的交换。
题目解析
现在题目要求我们进行两个数的交换,那首先我们需要有这两个数才行,所以我们要做的第一件事就是定义两个变量,用来存放这两个数;
交换两个数我们可以采用哪些方法呢?现在我们来思考一个情景:
小红和小明两个人手上抱着一箱书,他们现在要进行交换,小明先把自己手上的书放在了一边,然后接过了小红的书,之后小红抱起小明放在一边的书,他们就完成了交换;
在这个情景下,我们可以看到,小明是先把自己的手给空出来,再去接收小红的书,他们通过一个过渡存放处实现的交换,那换到我们的代码中是不是我们也可以通过一个中间变量来实现交换呢?
所以我们要做的第二步就是定义一个中间变量,来实现两数的交换;
我们有了存放两个数的变量,我们也有了过渡的中间变量,现在就只差我们的主角了,两个数,这里的两个数可以是整型,也可以是浮点型,简单一点,我们这里采用整数来完成这道题目。那现在问题来了,这个整数从哪里来?
有的朋友说我们可以直接给它们赋值两个整数,还有的朋友说我们可以给它们输入两个整数。不管是赋值也好,还是输入也好,这两种方式都是可以的,我们这里就简单点,直接给变量赋值就行,这里我们讲解就以整数1和2为例;
我们在有了变量和数之后就可以进行交换了,参照小红和小明的方法,这里我们可以通过赋值的方式来完成,将其中一个变量的值赋值给中间变量,将另一个变量的值赋值给这个变量,最后将中间变量的值赋值给另一个变量;
完成交换后我们要怎么知道它们交换成功没有呢?所以最后我们还要将结果输出在屏幕上才行,这时就需要用到printf函数来进行打印;
经过上面的分析,现在我们总结一下我们要实现两数交换需要进行的步骤:
步骤一:定义两个整型变量来存放两个整数 步骤二:定义一个中间变量来完成交换 步骤三:给两个整数进行赋值 步骤四:进行赋值交换 步骤五:打印结果
现在有了具体的思路,我们就可以开始进行代码编写了;
代码实现
代码语言:javascript复制#include <stdio.h>
//完成两数的交换
int main()
{
int a = 0, b = 0;//用来存放两个整数
int c = 0;//中间变量,进行辅助交换
a = 1, b = 2;//给两个变量进行赋值
//这里我们添加一个打印来与完成交换后的值进行对比
printf("a=%d,b=%dn", a, b);
//通过赋值进行交换
c = a;
a = b;
b = c;
//打印结果
printf("a=%d,b=%dn", a, b);
return 0;
}
接下来我们来看一下打印结果,看看代码是否完成了两数的交换:
从结果中我们可以看到,在完成赋值后,变量a和变量b分别存储的是1和2;在完成交换后,变量a和变量b的值完成了交换,a此时存储的是2,b此时存储的是1;
现在我们通过中间变量实现了两个数的交换,但是题目中要求的是不借助中间变量,这时我们又应该如何处理呢?
这里我给大家介绍一个思维——数学思维。什么意思呢?
对于这道题我们可以用数学的方法来实现,比如加法,我们可以将变量a和变量b的和赋值给变量a,然后再将赋值后的a与b的差赋值给b,此时b的值就是b=a b-b=a
,接下来我们再将a与赋值后的b的差值再赋值给a,此时a的值就是a=a b-a=b
,像这样我们就实现了两数的交换,代码如下:
//完成两数的交换
int main()
{
int a = 0, b = 0;//用来存放两个整数
a = 1, b = 2;//给两个变量进行赋值
//这里我们添加一个打印来与完成交换后的值进行对比
printf("a=%d,b=%dn", a, b);
//通过赋值进行交换
a = a b;
b = a - b;
a = a - b;
//打印结果
printf("a=%d,b=%dn", a, b);
return 0;
}
下面我们来打印一下结果验证一下:
此时我们可以看到,我们现在也完成了两数的交换,而且没有借助第三个临时变量;
现在我们已经完成了题目的要求,我们第一种方式借助临时变量通过赋值操作符完成了两数的交换,第二种方法我们没有借助临时变量通过算术操作符和赋值操作符完成了两数的交换,那我们还有没有其它的方法能实现呢?接下来我们就来介绍一下;
思维拓展
我们现在思考一个问题,我们在前面采用加法进行的交换有没有什么缺陷?
有朋友很快就想到了,我们通过加法赋值的对象是一个整型,但是整型的空间大小是有限的;
一个整型所占空间大小是4个字节,它能存储的数值的范围是-231~ 231-1,如果我们此时要交换的是231-2和231-1这两个整数,此时在进行加法后是不是就会导致算术溢出,此时的数值太大了,一个整型已经装不下了。那为了避免这个问题的出现,我们还有没有其它的方法呢?
这里我们要介绍的是借用位操作符来实现,我们现在来回忆一下按位与、按位或和按位异或的运算规则:
按位与:操作数对应的二进制位都为1,结果才为1,否则结果为0; 按位或:操作数对应的二进制位都为0,结果才为0,否则结果为1; 按位异或:操作数对应的二进制位不相同,结果才为1,否则为0;
也就是说进行位操作时,结果只是在0和1进行互换,并不会出现进位的情况,那这样是不是就避免了算术溢出的发生。
那这三个位操作符,我们选择哪个会比较合适呢?
我们来思考一下,如果使用按位与会是什么情况?如果使用按位或会是什么情况?如果使用按位异或又会是什么情况?
下面我们以交换5和3这两个整数为例,手写一下运算过程;
不管是按位与、按位或、还是按位异或,只要对应的二进制位都为0,结果肯定为0,所以这里我们就只展示不为0的字节:
按位与
代码语言:javascript复制int main()
{
int a = 0, b = 0;
a = 5;//0101
b = 3;//0011
a & b;//0001
return 0;
}
现在我们经过按位与之后我们得到了0001这个二进制序列,我们想要得到5和3的二进制序列,我们应该如何操作呢?
根据位操作的规则,此时我们想得到5,我们可以采用按位或一个5,可以采用按位异或一个4;那想得到3也是同理,我们可以按位或一个3,或者按位异或一个2,具体操作如下:
代码语言:javascript复制int a = 0, b = 0;
a = 5;//0101
b = 3;//0011
a = a & b;//0001——1
// 1 : 0001
// 5 : 0101
b = a | 5;//0101——5
// 1 : 0001
// 3 : 0011
a = a | 3;//0011——3
// 1 : 0001
// 4 : 0100
b = a ^ 4;//0101——5
// 1 : 0001
// 2 : 0010
a = a ^ 2;//0011——3
可以看到,确实可以实现,下面我们再来看按位或又是怎么操作的;
按位或
代码语言:javascript复制int main()
{
int a = 0, b = 0;
a = 5;//0101
b = 3;//0011
a | b;//0111
return 0;
}
此时我们通过按位或得到了7,我们要得到5,可以采用按位与一个5,或者按位异或一个2;那么想得到3也是同理,我们可以按位与一个3,或者按位异或一个4具体操作如下:
代码语言:javascript复制int a = 0, b = 0;
a = 5;//0101
b = 3;//0011
a = a | b;//0111
// 7 : 0111
// 5 : 0101
b = a & 5;//0101——5
// 7 : 0111
// 3 : 0011
a = a & 3;//0011——3
// 7 : 0111
// 2 : 0010
b = a ^ 2;//0101——5
// 7 : 0111
// 4 : 0100
a = a ^ 4;//0011——3
可以看到,我们也得到了5,接下来我们来看看按位异或是如何操作的;
按位异或
代码语言:javascript复制int main()
{
int a = 0, b = 0;
a = 5;//0101
b = 3;//0011
a ^ b;//0110
return 0;
}
经过按位异或之后我们得到了6,此时我想得到5,我可以按位异或一个3,我想得到3,我可以按位异或一个5,操作如下:
代码语言:javascript复制int a = 0, b = 0;
a = 5;//0101
b = 3;//0011
a = a ^ b;//0110——6
// 6 : 0111
// 3 : 0011
b = a ^ b;//0101——5
// 6 : 0111
// 5 : 0101
a = a ^ b;//0011——3
从这三次操作我们可以对比一下,按位与和按位或如果想使用原始的数据,那么就需要借助至少一个变量来完成交换的操作,以按位与举例,如图所示:
可以看到,此时我们想要完成这个操作,我们不仅借助了一个变量c,我们还借助了数字2,那现在问题来了,这个数字2是我们通过观察二进制序列得到的,如果换成其它的数,我们每一次都要先观察二进制序列,才能得出结论,这样显然是不合理的;
那如果想通过按位与实现,我们就需要借助两个变量来存储a和b的初始值,如下图所示:
可以看到,此时我们也是能够实现的,只不过我们需要借助两个变量才行,按位或也是同理;
但是我们观察按位异或会发现,它并不需要多余的变量就能实现,如下图所示:
可以看到此时我们不仅没有依靠多余的变量就完成了两数的交换,而且还避免了溢出的问题,此时我们就很好的完成了题目的要求。代码如下:
代码语言:javascript复制//完成两数的交换
int main()
{
int a = 0, b = 0;//用来存放两个整数
a = 1, b = 2;//给两个变量进行赋值
//这里我们添加一个打印来与完成交换后的值进行对比
printf("a=%d,b=%dn", a, b);
//通过位操作进行交换
a = a ^ b;
b = a ^ b;
a = a ^ b;
//打印结果
printf("a=%d,b=%dn", a, b);
return 0;
}
这一题就介绍完了,现在大家是不是对位操作符的使用有了新的思路呢?下面我们接着来看下一题;
2.编写代码实现:求一个整数存储在内存中的二进制中1的个数。
这一题看到题目,就感觉,恩,有点东西!好像一点头绪都没有。那是不是我们碰到这个题就可以直接放弃了呢?那肯定不行。
在解答这一题之前,我们先来简单分析一下这个题目;
题目解析
这个题目的要求是求二进制中1的个数,看到二进制,我们能够想到与之有关的操作符是不是有上一题中我们提到的位操作符,还有一个我们在知识点讲解中提到的移位操作符呀!
那现在目标就很明确了,既然涉及到二进制位,那我们肯定得借助这两类操作符中至少一类操作符,我们又应该如何借助它们来求解呢?别着急,我们继续往下分析;
整数所占空间大小
题目中要求的是一个整数,我们学过的整数在空间中所占空间大小是什么情况呢?
对于短整型来说,短整型的整数所占空间大小为2个字节; 对于整型来说,整型的整数所占空间大小为4个字节; 对于长整型来说,长整型的整数所占空间大小根据操作系统的不同而有不同的大小——在32位操作系统中占4个字节,在64位操作系统中占8个字节;(g 严格遵守这个规定,但是在VS的环境下,长整型所占空间大小是4个字节) 对于长长整型来说,长长整型的整数所占空间大小为8个字节;
这里我们以整型类型的整数为例来解答此题,因此我们要使用的整数所占空间大小为4个字节,也就是32个比特位。
判断1的方式
现在我们确定了我们要使用的整数所占空间大小,接下来我们就要开始思考如何来计算整数的二进制位中1的个数了。
接下来我们继续思考,如果我们要计算一件事物的个数,就比如我要计算我家里有多少碗,我应该怎么做呢?是不是我需要一个一个的数过去才能知道我家里有多少碗呀。同理,我如果想要知道二进制位中1的个数,我是不是也应该一个一个的数过去我才能知道总共有多少个1呀。
我们应该怎么做才能得到二进制位上的每一个数,并且还能知道它这个数是什么呢?
我们来回想一下位操作符与移位操作符的作用:
位操作符:
对两个操作数对应的二进制位进行运算
移位操作符:
对左操作数的二进制位进行移动: 左移空缺部分用0填补 逻辑右移,空缺部分用0填补 算术右移,正整数空缺部分用0填补,负整数空缺部分用1填补
根据这两类操作符的作用我们来思考一下,我们能不能通过移位操作符将整数的二进制位一个一个移动,每次移动时判断它是否为1,直到将32个比特位全部移动完。想法是好的,那我们应该如何判断呢?
我们现在的目标是计算1的个数,在为操作符中我们知道按位与是只有对应二进制位都为1,结果才为1,那我们可不可以让被判断的二进制位与1进行按位与,进而判断该二进制位是否为1呢?
那接下来我们就可以尝试一下,借助移位操作符来对二进制位进行移动,借助按位与操作符来判断二进制位是否为1。
现在我们只需要确定两件事——移动方向、判断位数;
判断1的过程
移动方向的确定
在有符号整型中符号位是不参与运算的,如果是正数,符号位为0,如果是负数,符号位为1,也就是是说不管我是左移右移,对于有符号的负数,我都得给计算结果 1;
对于正数来说,我要进行左移的话那我就得在数值位的最高位进行判断,对于右移的话,我要在数值位的最低位进行判断;
对于负数来说,因为左移时低位补0,右移时高位补1,那我们要判断负数的1的个数,左移才是最优的选择,
判断位数的确定
在确定了移动方向之后,现在我们要确定的是判断的位数。有朋友就说了,我直接给所有的位数直接判断不就好了吗?也就是对不同的数我们直接进行按位与231-1
也就是二进制位按位与0111 1111 1111 1111 1111 1111 1111 1111
,那根据对应的结果我们不就知道有多少个1了吗?
那现在问题来了,根据231-1
的二进制位,我们不难看出,不管他与谁进行按位与,根据按位与的规则,结果都将是原来的那个数。
我们可以简单的做个测试:
我们可以从测试结果中看到,我这样做就有点多此一举,我现在要通过这个结果求的就是这个数二进制位中1的个数,现在的情况是按位与后还是这个数本身,所以我们这不就相当于没有求解吗。所以按位与231-1
肯定是不太合适的;
既然我们不能一次判断所有的二进制位,那我们只能选择一个或多个的进行判断了,对于不同的数据类型来说我们的代码都要能判断的话,最好的方式就是判断的位数为它们所占空间大小的公约数,
对于不同的整型来说,它们所占空间大小有8个比特位、32个比特位以及64个比特位,如果是一次性判断8个比特位,那我们在判断short类型时也会出现与判断int类型时一样的问题,那我们接下来可选择的就只有一次性判断1/2/4这三种情况的比特位了;
也就是说,如果是正整数左移,我们就要判断的是左边的1/2/4这三种情况的比特位;
那对于short类型,它需要按位与的就是26 /26 25 /26 25 24
对于int/long类型,它需要按位与的就是230 /230 229 /230 229 228
对于long long类型,它需要按位与的就是262 /262 261 /262 261 260
对于负整数刚才我们也分析了,它只能通过左移来判断,所以对于不同类型的负整数来说,它需要判断的情况就是在正整数左移按位与的数加上一个负号,也就是:
那对于short类型,它需要按位与的就是-26 /-(26 25 )/(26 25 24 )
对于int/long类型,它需要按位与的就是-230 /-(230 229 )/-(230 229 228 )
对于long long类型,它需要按位与的就是-262 /-(262 261 )/-(262 261 260 )
如果是正整数右移,我们需要判断的是右边的1/2/4这三种情况的比特位;
对于short类型,它需要按位与的就是1/3/15
对于int/long类型,它需要按位与的就是1/3/15
对于long long类型,它需要按位与的就是1/3/15
这样一对比,显然对于正整数来说右移才是最佳的选择,因为负整数的情况比较特殊,所以我们这一题就不讨论负整数的1的判断;
下面我们来测试一下,将1-15之间的数来与它们进行按位与:
当判断4个比特位时,我们需要将1-15的二进制序列先列举出来再进行判断:
1个1的情况:
0001——1;0010——2;0100——4;1000——8
2个1的情况:
0011——3;0101——5;1001——9;0110——6;1010——10;1100——12
3个1的情况:
0111——7;1011——11;1101——13;1110——14
4个1的情况:
1111——15
从结果上来看确实能够反应1的个数,只不过我们在使用时,需要将各个情况枚举出来进行一一对照
在进行2个比特位判断时,此时的情况比较少,借鉴上面的枚举结果我们不难得出
结果是1和2就说明有1个1,结果是3就说明有两个1;
但是在与1进行按位与时,它只需要判断当前的这个二进制位是否为1就行;
所以从结果上来看,一个一个来判断显然更合理一点,但是2个或者4个来进行判断也是没什么问题的,只不过此时需要将可能的情况全部枚举出来进行对照才行;
经过上面的分析,现在我们就很明确这一题的解题思路了,要完成这一题的求解我们需要有以下几个功能:
功能一——有一个整型存储空间,也就是定义一个整型变量来存储整数 功能二——获取每一个二进制位,这里我们可以通过循环和移位实现 功能三——判断二进制位是否为1,这里我们通过与1进行按位与实现判断 功能四——记录1的个数,这里我们可以通过计数变量来实现 功能五——打印结果,这里我们通过printf函数来实现
有了具体的思路,我们就可以进行代码编写了;
代码实现
功能一——有一个整型存储空间
这个功能的实现是比较简单的,我们只需要定义一个整型变量来存储整数就行,获取整数的方式我们这里采用scanf函数来获取;
代码如下:
代码语言:javascript复制//功能一——整型存储空间
int a = 0;
scanf("%d", &a);
功能二——获取每一个二进制位
这个功能我们是通过循环与移位来实现,考虑到不同的类型所占空间大小不同,所以我们可以使用sizeof来计算变量所占空间大小,从而决定循环次数,代码如下:
代码语言:javascript复制//功能二——获取每一个二进制位
for (int i = 0; i < sizeof(a) * 8; i )//通过sizeof来计算当前整型所占空间大小
{
//通过移位移走以及判断过的二进制位
a >>= 1;
}
功能三——判断二进制位是否为1
这个功能我们在分析中已经详细介绍过了,这里我们直接将变量与1进行按位与并判断结果是否为1,代码如下:
代码语言:javascript复制//功能三——判断二进制为是否为1
if (1 == (a & 1))
这里一定要记得用圆括号将按位与的部分括起来,以此来告诉计算机,我要先计算按位与,再判断是否等于1,这样表达式就不会出现问题;
功能四——记录1的个数
这个功能就比较简单了,我们可以通过计数变量,并在每次判断完之后来决定是否改变计数变量的大小就行了,代码如下:
代码语言:javascript复制//功能一——整型存储空间
int a = 0;
scanf("%d", &a);
//定义计数变量
int count = 0;
//功能二——获取每一个二进制位
for (int i = 0; i < sizeof(a) * 8; i )//通过sizeof来计算当前整型所占空间大小
{
//功能三——判断二进制为是否为1
if (1 == (a & 1))
{
//功能四——记录1的个数
count ;
}
//通过移位移走以及判断过的二进制位
a >>= 1;
}
功能五——打印结果
最后就是结果打印了,我们只需要再循环外将计数变量的结果打印出来就行,这里我们为了让结果打印的更具体一点,我们可以通过增加一个中间变量来存放最开始的整数值,代码如下:
代码语言:javascript复制//功能一——整型存储空间
int a = 0;
scanf("%d", &a);
int b = a;//这里定义一个中间变量来完成结果打印
//定义计数变量
int count = 0;
//功能二——获取每一个二进制位
for (int i = 0; i < sizeof(a) * 8; i )//通过sizeof来计算当前整型所占空间大小
{
//功能三——判断二进制为是否为1
if (1 == (a & 1))
{
//功能四——记录1的个数
count ;
}
//通过移位移走以及判断过的二进制位
a >>= 1;
}
//功能五——打印结果
printf("%d的二进制位中有%d个1n", b, count);
到这里我们的代码基本上就完成了,但是呢,我想给这个代码加一个附加功能,让它可以多次判断不同的整数,这又应该如何实现呢?
附加功能——多次判断
这个功能的实现比较陌生,我们前面还未接触过,现在我直接告诉大家方法——我们可以通过将输入语句也就是scanf函数放入循环的判断条件内,通过输入函数的返回值来控制循环是否继续;
对于scanf函数与多组输入,后面我们花一个篇章来特别介绍,所以大家不用着急,现在我们只需要知道当scanf函数能够正常读取时,它的返回值与变量的个数相等就行。
这里我们只有一个变量a我们就只需要判断它的返回值是否为1,代码如下:
代码语言:javascript复制//求一个整数存储在内存中的二进制中1的个数
int main()
{
//功能一——整型存储空间
int a = 0;
//附加功能——通过多组输入实现多次判断
while (scanf("%d", &a) == 1)
{
int b = a;//这里定义一个中间变量来完成结果打印
//定义计数变量
int count = 0;
//功能二——获取每一个二进制位
for (int i = 0; i < sizeof(a) * 8; i )//通过sizeof来计算当前整型所占空间大小
{
//功能三——判断二进制为是否为1
if (1 == (a & 1))
{
//功能四——记录1的个数
count ;
}
//通过移位移走以及判断过的二进制位
a >>= 1;
}
//功能五——打印结果
printf("%d的二进制位中有%d个1n", b, count);
}
return 0;
}
下面我们来看一下运行效果:
可以看到,我们不仅很好的完成了判断整数1的个数,我们还完成了判断多个整数。
现在我们这一题就解决了,这个题目我们还可以进行完善,比如我们可以在判断完后将二进制位给打印出来,这个思路就是,我们将每次判断后的结果给存储起来,直到所有的二进制位都判断完,我们再将存储的结果打印出来就行。具体怎么实现,有兴趣的朋友可以下去尝试一下;
3.360笔试题——程序输出的结果是什么?
现在我们来介绍最后一道题,请看下面的代码:
代码语言:javascript复制#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 = %dn b = %dn c = %dnd = %dn", a, b, c, d);
return 0;
}
//程序输出的结果是什么?
在解决这一题之前我们要清楚题目在考察我们什么内容?
考察内容
从题目中可以看到,这里使用的操作符位后置 、前置 、逻辑与和逻辑或,这四个操作符的优先级如下表所示:
优先级 | 操作符 |
---|---|
1 | 后置 |
2 | 前置 |
3 | 逻辑与&& |
4 | 逻辑或|| |
要注意,优先级是相对于两个相邻的操作符来说的,并不是对于整个表达式来说的,也就是说a 和d 并不是同时运行;
也就是说在这题中对于a 与逻辑与和逻辑或来说先运行的是后置 ;
对于逻辑与和逻辑或与 b来说,先运行的是 b;
对于逻辑与和逻辑或与d 来说,先运行的是d ;
因此这个表达式的运行逻辑如下表所示:
运行步骤 | 运行区块 | 运行表达式 |
---|---|---|
1 | a && /a || | a |
2 | && b /|| b | b |
3 | b && / b || | b |
4 | && d /|| d | d |
这个顺序是根据逻辑操作符的结合性决定的,对于 b的区块来说,不管是与前面的逻辑操作符进行比较还是与后面的逻辑操作符进行比较,前置 的优先级都是高于逻辑操作符的,所以在这个区块中有限运行 b,在表格中我用两步来表示的意思是为了区分前面的逻辑操作符和后面的逻辑操作符,并不是说 b执行两次,这个点要注意,别理解错咯。
下面按照这个思路那我们可以得到的结果是:
a=1,b=3,c=3,d=5
但是具体结果是不是这样呢?下面我们来通过VS看一下运行结果:
可以看到,最后程序输出的结果并不是前面我们计算的那样,这是为什么呢?
其实这道题考察的是我们在介绍逻辑操作符时介绍过的一个小的知识点——短路;
什么是短路?现在我来帮大家回忆一下:
在进行逻辑与运算时,如果左边表达式结果为假,则右边不再进行运算; 在进行逻辑或运算时,如果左边表达式结果为真,则右边不再进行运算;
在弄清楚了考察内容后,我们再来分析一下题目;
题目解析
在int i = 0,a=0,b=2,c =3,d=4;
这个条件下,不管是表达式i = a && b && d ;
,
还是表达式i = a || b||d
,此时我们的运算顺序是确定的,先计算的是a ;
后置 的特点是先使用,再自增,因此我们可以对这两个表达式进行进一步的分析;
对于表达式i = a && b && d ;
来说,此时a=0,根据后置 的特点,我们先用0来进行判断,在计算机中0代表着假,非0代表真,此时第一个逻辑与的左侧表达式已经为假了,所以后面的内容就不再进行运算了,接着我们再对a进行自增,也就得到了最终的结果:a=1,b=2,c=3,d=4;
对于表达式i = a || b||d
来说,此时a=0,根据后置 的特点,我们先用0来进行判断,此时0为假,所以在a进行自增后, b可以正常执行,但是 b执行后的结果为3表示真,此时对于第二个逻辑或,它左侧的表达式已经为真了,右侧就不再进行判断了,因此最终的结果应该为:a=1,b=3,c=3,d=4
。下面我们通过VS来验证一下:
可以看到结果确实如此。从这里我们可以看到,这道题很好的考察了逻辑操作符的短路,下面我们如果将题目稍作修改,又会得到怎样的结果呢?
思维拓展
接下来我们来修改一下代码:
代码语言:javascript复制#include <stdio.h>
int main()
{
int i = 0,a=1,b=2,c =3,d=4;
i = a && b && d ;
//i = a || b||d ;
printf("a = %dn b = %dn c = %dnd = %dn", a, b, c, d);
return 0;
}
//程序输出的结果是什么?
可以看到,这次我们把a的初始值变为了1,那这两个表达式的运行结果又会是怎样的呢?
我们还是按照刚刚的逻辑来求解;
在逻辑与表达式中,对于第一个区块a &&
来说,先计算后置
,因为后置
是先使用,再 ,所以先使用a=1
,此时表达式为真,a再进行自增,a=2
,之后开始判断第二部分&& b
;
对于第二个区块&& b
来说,先计算前置
,因为前置
是先 ,再使用,所以先对b进行 ,b=3
,再使用2 && 3
,表达式左右两边都为真,表达式结果为真,此时a && b
整体表达式的值为1;
对于第三个区块1 && d
来说,先计算后置
,所以此时先使用d=4
,此时表达式为真,d再进行自增,d=5
,之后开始判断1 && 5
,表达式为真,所以整个表达式的值为1,也就是i=1
;
但是在逻辑或的表达式中,对于第一个区块a 和逻辑或来说,此时先使用a值,a=1,表达式为真,逻辑或表达式发生短路,右侧表达式则不再计算,此时整个表达式的结果为真,也就是i=1
;
所以逻辑与的表达式结果为:a=2,b=3,c=3,d=5,i=1
,逻辑或的表达式结果为:a=2,b=2,c=3,d=4,i=1
;我们在VS中运行一下,看看结果:
可以看到,结果和我们分析的一样,我相信大家现在对逻辑操作符的短路以及表达式的求值顺序应该有了更加清晰的理解了。如果大家还有什么疑问,可以在评论区留言哦!
结语
咱们今天的习题到这里就全部介绍完了,希望今天的内容能帮助大家更好的理解操作符以及表达式求值的相关知识点。最后,感谢各位的翻阅,接下来随着学习的深入,我会继续给大家分享我在学习过程中的感受,咱们下一篇见!!!