学习笔记5:关于操作符与表达式的求值

news/2024/10/30 15:26:18/

目录​​​​​​​

一.移位操作符

1.左移操作符

2.右移操作符

二.位操作符

1.位运算基本知识

2.位运算的巧妙运用

 三.其他操作符

1.算术操作符

2.单目操作符

3.关于逻辑操作符

 四.表达式求值

隐式类型转换

(1)整形提升(短整型家族数据的二进制序列补位转换)

(2).算术转换


一.移位操作符

<< 左移操作符
>> 右移操作符
注:移位操作符的操作数只能是整数。

1.左移操作符

移位规则:
左边抛弃、右边补0.

2.右移操作符

移位规则:
首先右移运算分两种:
(1). 逻辑移位
左边用0填充,右边丢弃
(2). 算术移位
左边用原该值的符号位填充,右边丢弃

在vs2022编译器中,整形数据的右移操作执行的是算术右移,即移动后,左边的二进制位用原数据的符号位来填充。

二.位操作符

1.位运算基本知识

&
|
^
按位与
按位或
按位异或

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

按位与&:两个整数对应的二进制位如果同为1则该位的运算结果为1,否则为0

按位或|两个整数对应的二进制位只要有一个1则该位的运算结果为1,否则为0

按位异或^:两个整数对应的二进制位不同则该位的运算结果为1,否则为0

位运算按位与,按位或,按位异或都满足交换律和结合律。位运算的交换律和结合律有十分巧妙的运用。

2.位运算的巧妙运用

leetcode645. 错误的集合问题描述:

leetcode链接:645. 错误的集合 - 力扣(Leetcode)

集合 s 包含从 1 到 n 的整数。不幸的是,因为数据错误,导致集合里面某一个数字复制了成了集合里面的另外一个数字的值,导致集合丢失了一个数字 并且 有一个数字重复 。

给定一个数组 nums 代表了集合 S 发生错误后的结果。

请你找出重复出现的整数,再找到丢失的整数,将它们以数组的形式返回。

题解函数接口定义:int* findErrorNums(int* nums, int numsSize, int* returnSize)

nums是给定的数组的首地址

numsSize是数组的元素个数

returnSize是记录返回的数组(需要动态内存分配函数来开辟)的元素个数的变量的地址

注意给定的数组是乱序的

本题的其中的一个求解办法就是用位运算:

第一步:利用按位异或运算的可交换性和可结合性可以得到丢失的数字和重复的数字两个数字的按位异或的结果。

我们将题目给定的错误的数组和正确的原数组两个数组中的所有元素进行按位异或的运算。

	int Norsum = 0;int i = 0;for (i = 0; i < numsSize; i++){Norsum ^= nums[i];Norsum ^= (i + 1);}

Norsum中便记录了数组中重复的数字和丢失的数字按位异或的结果

Norsum的二进制序列相当于记录了重复的数字和丢失的数字的二进制序列的不同位

第二步:

取出Norsum中的最低位的1记录在lowbit变量中:

	int Norsum = 0;int i = 0;for (i = 0; i < numsSize; i++){Norsum ^= nums[i];Norsum ^= (i + 1);}int lowbit = Norsum &(-Norsum);

此时lowbit就记录了重复的数字和丢失的数字两个正整数二进制序列最低的不同位。

第三步:将题目给定的数组和正确的原数组的所有元素逐一与lowbit进行按位&运算,由于lowbit的二进制序列只有一位为1,所以每次按位与运算的结果要么为0要么等于lowbit。

 

	int Norsum = 0;int i = 0;for (i = 0; i < numsSize; i++){Norsum ^= nums[i];Norsum ^= (i + 1);}int lowbit = Norsum &(-Norsum);int x = 0;int y = 0;for (i = 0; i < numsSize; i++){if (0 == lowbit & nums[i]){x ^= nums[i];}else{y ^= nums[i];}}for (i = 0; i < numsSize; i++){if (0 == lowbit & (i + 1)){x ^= (i + 1);}else{y ^= (i + 1);}}

两组元素分别按位异或后结果存放在x和y两个变量中,由于相同的元素必然分到同一组,重复的元素和丢失的元素必然被分到不同组所以最终x和y分别为丢失的元素和重复的元素的其中一个,但是无法确定x,y与丢失元素和重复元素的具体对应关系,最后只需再遍历一次nums数组确定这个对应关系即可。

	int* Return = NULL;if ((Return = (int*)malloc(sizeof(int) * 2)) == NULL){printf("malloc failed\n");exit(0); }

开辟一个两个元素的数组来存储结果:Return[0]存放重复的元素

                                                           Return[1]存放丢失的元素

再遍历一次nums数组确定x和y与丢失的元素和重复的元素的对应关系。

	int flag = 1;                       用flag来标记x是否为重复的元素for (i = 0; i < numsSize; i++)      确定x是否存在于nums数组中。{if (x == nums[i]){Return[0] = x;Return[1] = y;flag = 0;}}if (flag){Return[0] = y;Return[1] = x;}

题解代码:

int* findErrorNums(int* nums, int numsSize, int* returnSize)
{int Norsum = 0;int i = 0;for (i = 0; i < numsSize; i++){Norsum ^= nums[i];Norsum ^= (i + 1);}int lowbit = Norsum &(-Norsum);int x = 0;int y = 0;利用lowbit将两组元素分成两组分别以按位异或的方式存入x和y中。                                for (i = 0; i < numsSize; i++) {if (0 == (lowbit & nums[i])){x ^= nums[i];}else{y ^= nums[i];}}for (i = 0; i < numsSize; i++){if (0 == (lowbit & (i + 1))){x ^= (i + 1);}else{y ^= (i + 1);}}int* Return = NULL;if ((Return = (int*)malloc(sizeof(int) * 2)) == NULL){printf("malloc failed\n");exit(0);}int flag = 1;                       用flag来标记x是否为重复的元素for (i = 0; i < numsSize; i++)      确定x是否存在于nums数组中。{if (x == nums[i]){Return[0] = x;Return[1] = y;flag = 0;}}if (flag){Return[0] = y;Return[1] = x;}*returnSize = 2;return Return;
}

 三.其他操作符

1.算术操作符

+        -     *       /       %
1. 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。
2. 对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。
3. % 操作符的两个操作数必须为整数。返回的是整除之后的余数。

2.单目操作符

!
-
+
&
sizeof
~
--
++

*

(类型)

逻辑反操作
负值
正值
取地址
操作数的类型长度(以字节为单位)
对一个数的二进制按位取反
前置、后置--
前置、后置++

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

强制类型转换

注意sizeof(类型名)时括号不能省

3.关于逻辑操作符

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

逻辑操作符尤其要注意的是逻辑短路规则:

比如:

对于逻辑与 &&(且)表达式:a&&b,若表达式a为假(值为0),则计算机不会计算表达式b,整个表达式的结果为0;

对于逻辑或 ||   (或)表达式:a||b   ,若表达式a为真(值为非0),则计算机不会计算表达式b,整个表达式结果为1;

代码段中的表达式i = a++ && ++b && d++;

由于a++是先访问a的值再完成a的自增,所以a++表达式的值为0,基于逻辑短路规则,&&后面的逻辑表达式不会再计算,i的值被赋为0,a自增为1,b,d的值不变,因此打印的结果为a=1,b=2,c=3,d=4;

 四.表达式求值

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

只要有表达式,我们就要考虑类型转换的问题。

隐式类型转换

(1)整形提升(短整型家族数据的二进制序列补位转换)

C的整型算术运算总是至少以缺省整型类型的精度来进行的.

为了获得这个精度,表达式中的字符和短整型等(字节数小于整形int)操作数在使用之前被转换为普通整型(二进制序列补到与int同位数),这种转换称为整型提升。

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

对于有符号的短整型家族数据,整形提升时二进制序列高位补符号位。

对于无符号的短整型家族数据,整形提升时二进制序列高位补0。

相关实例: 

阅读代码预测输出结果:

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;
}

0xb6和0xb600都是整形数据存入a和b都会发生截断

0xb6对应的二进制序列为:        00000000000000000000000010110110

0xb600对应的二进制序列为:    00000000000000001011011000000000

0xb6截断后存入a中的序列:    10110110(最高位被视为符号位)

0xb600截断后存入b中的序列:1011011000000000(最高位被视为符号位)

a==0xb6比较时a发生整形提升高位补1,结果不再相同.

b==0xb600比较时a发生整形提升高位补1,结果不再相同.

所以最后只打印c

相关实例: 

阅读代码预测输出结果:

int main()
{char c = 1;printf("%u\n", sizeof(c));printf("%u\n", sizeof(+c));printf("%u\n", sizeof(-c));return 0;
}

+c和-c都是运算表达式.

因此sizeof(+c),sizeof(-c)中c会发生整形提升算出来结果为4个字节。(VS2022整形为4个字节)。

相关实例: 

阅读代码预测输出结果:

int main()
{unsigned char a = 200;unsigned char b = 100;unsigned char c = 0;c = a + b;printf(“%d %d”, a+b,c);return 0;
}

200截断存入a中的二进制序列为:1100 1000

100截断存入b中的二进制序列为:0110 0100

c= a+b 运算时a和b都要发生整形提升,由于是无符号数所以高位补0。

a+b整形提升后运算结果为:00000000000000000000000100101100

截断后存入c中的二进制序列:      00101100(转换为十进制为44)

a+b和c打印前a,b,c也要发生整形提升。

最后打印结果为:300 44

(2).算术转换

且某个操作符的各个操作数的大小如果都大于或等于整形,且各个操作数属于不同的类型,那么除非其中一些操作数的转换为另一个操作数的类型,否则操作就无法进行。这种转换称为算术转换。

算术转换的原则是小的,精度低的数据类型向更大,精度更高的数据转换(向上转换原则)。

相关实例: 

阅读代码预测输出结果:

int main()
{int i;i--;if (i > sizeof(i)){printf(">\n");}else{printf("<\n");}return 0;
}

sizeof()操作符返回的结果为无符号整形(unsigned int) .

注意i为全局变量,会默认初始化为0.

所以i和sizeof(i)比较时i会发生算术转换,转换为无符号数,而i原本为-1,二进制序列为三十二个1,所以转换为无符号数后是一个非常大的正数。所以程序会输出>


http://www.ppmy.cn/news/10297.html

相关文章

TCP中的状态转移(三种情况)

文章目录前言一、 TCP的生命周期二、另外两种挥手情况三、经典四问总结前言 博主个人社区&#xff1a;开发与算法学习社区 博主个人主页&#xff1a;Killing Vibe的博客 欢迎大家加入&#xff0c;一起交流学习~~ 在正常情况下&#xff0c;TCP要经过三次握手建立连接&#xff0c…

第一章 Flink简介

Flink 系列教程传送门 第一章 Flink 简介 第二章 Flink 环境部署 第三章 Flink DataStream API 第四章 Flink 窗口和水位线 第五章 Flink Table API&SQL 第六章 新闻热搜实时分析系统 前言 流计算产品实时性有两个非常重要的实时性设计因素&#xff0c;一个是待计算…

uni微信小程序,打开地图,跳转第三方

一、需求 微信小程序 需要点击并跳转第三方地图软件导航&#xff0c;并计算到目标位置距离 二、思路 思路&#xff1a; 1.接口返回需要有位置的经纬度&#xff0c;这个自行在后台编辑获取 2.需要获取用户的位置权限 我这边使用的是uniapp&#xff0c;需要使用官方封装两个…

【python 基础篇 五】python的常用数据类型操作-------列表

目录1.列表的基本概念和定义2.列表的常用操作2.1 列表的增加操作2.2 列表的删除操作2.3 列表的修改操作2.4 列表的查找操作2.5 列表的遍历操作2.6 列表的判断和比较操作2.7 列表的排序操作2.8 列表的乱序和反转操作1.列表的基本概念和定义 概念&#xff1a;有序的可变的元素集…

c++11 标准模板(STL)(std::forward_list)(一)

定义于头文件 <forward_list> template< class T, class Allocator std::allocator<T> > class forward_list;(1)(C11 起)namespace pmr { template <class T> using forward_list std::forward_list<T, std::pmr::polymorphic_…

指针进阶篇(1)

目录 &#x1f914; 前言&#x1f914; 一、&#x1f60a;字符指针&#x1f60a; 二、&#x1f61c;指针数组&#x1f61c; 三、&#x1f61d;数组指针&#x1f61d; 3.1数组指针的定义 3.2&数组名VS数组名 3.3数组指针的使用 四、&#x1f31d;数组参数&#xff0c…

基础算法(二)——归并排序

归并排序 介绍 归并排序是一种复杂度O(nlog(n)nlog(n)nlog(n))的排序算法&#xff0c;并且在任何情况下都是&#xff0c;但是它不是原地算法&#xff0c;即需要额外存储空间 其原理是&#xff0c;先将区间均匀分成左右两半&#xff0c;然后再对左右两半继续二分&#xff0c;…

LeetCode 138. 复制带随机指针的链表(C++)

思路&#xff1a; 用哈希表实现&#xff0c;创建一个哈希表来对应原链表中的每一个节点&#xff0c;这样也可以将原链表中的所有结点的next和random关系映射到哈希表复制链表中。 原题链接&#xff1a;https://leetcode.cn/problems/copy-list-with-random-pointer/description…