- 操作符详解
- 算术操作符
- 移位操作符
- 1.左移操作符
- 2.右移操作符
- 位操作符
- 例题
- 赋值操作符
- 单目操作符
- 关系操作符
- 逻辑操作符
- 条件操作符
- 逗号表达式
- 下标引用操作符,函数调用,结构体成员访问
- 表达式求值
- 1.隐式类型转换
- 2.整形提升
- 3.算术转换(大小>=int)
移位操作符算术操作符有+ - * / %
其中加减乘都是双目操作符也即是有两个操作数,除法要注意,两边都是整数就执行整数除法,有一个为浮点数就执行浮点数除法,取模的操作数只能是整数
1.左移操作符左移<<
右移 >>
移位只能移动正整数位,不能移动负数位与浮点数位
浮点数不可以进行移位(因为浮点数在内存中的存储方式和整数不同)
2.右移操作符左移移动的是数字的二进制位
高位丢弃,低位补零。
左移相当于给数字乘上2
还要注意整数在内存中存储的是补码 所以移位操作移动的也是补码。
位操作符右移操作符涉及到符号位。
右边丢弃,左边分为逻辑右移和算术右移
逻辑右移比较简单,不管符号位,高位直接补零(虽说有这个概念,但是我没有见过有编译器使用逻辑右移)
算术右移
左边补符号位,右边丢弃。(近乎所有的编译器都是用算术右移)
位操作符有:&按位与
| 按位或
^按位异或
1.按位与,对两个数的二进制位进行比较,如果同为1,结果则为1,有一个为0,结果就为0.任何数&上-1都为本身
看个例子:(5和1按位与)
00000000 00000000 00000000 00000101(5的补码(正数的原码反码补码相同))
00000000 00000000 00000000 00000001(1的补码)
00000000 00000000 00000000 00000001(结果的补码)
2.按位或,对两个数的二进制进行比较,同为0,结果才为0.有一个为1,结果就为1.任何数|上0都为本身
看个例子:5按位与上8
00000000 00000000 00000000 00000101
00000000 00000000 00000000 00001000
00000000 00000000 00000000 00001101(结果的补码)
按位异或,对二进制位进行操作,两个数二进制位相同位为0,不同为1.所以两个相同数异或结果为0 任何数异或上0都是本身
例子:
00000000 00000000 00000000 00000101
00000000 00000000 00000000 00000111(7的补码)
00000000 00000000 00000000 00000010(结尾为2)
既然了解了位操作符我们来看一下,如何不创建变量交换两个变量的值
#includeint main() { int a = 10; int b = 8; //数学方法 a=a+b; b=a-b; a=a-b; //位操作方法 a=a^b; b=a^b; a=a^b; return 0; }
例题讲一下位操作的思路,要注意按位异或符合交换律,a的值位a^b;
a ^ b ^ b = a;
a ^ b ^ a = b;
如此便实现了两个变量的交换,但是日常写代码还是建议用第三个变量进行交换效率高,可读性高。
求一个整数存储在内存中的二进制中1的个数
//方法1 #includeint main() { int i = 0; int n = 23; int count = 0; for(i=0;i<32;i++) { if((n>>i)&1) { count++; } } printf("%d",count); return 0; }
那么这个方法有没有缺陷呢?当然有,那就是不管这个数字是什么我们都需要循环32次。
下面介绍一个简单方法。
//方法二 #includeint main() { int count = 0; int n = 23; while(n) { n=n&(n-1); count++; } printf("%d",count ); return 0; }
这样是不就喵喵了很多啊,数字二进制有多少个1,就循环多少次。但是其实很难想到。
使用位操作将二进制位的某一位置1
int a =13; a=a | (1<<4);//将a的第四位置为1; //将二进制位的某一位置为0也是同样的道理 a=a & (~(1<<4))//因为任何数与上1都是不变的,所以我 只需要将要置零的那一位的二进制位置为0,其余的二进制位全是1即可赋值操作符
赋值操作符就简单了很多,
当然还有连续赋值操作符
int a; a=10;//这就是赋值操作符,给以及创建好的变量附上一个数值 int b =20; int c = 30; a=b=c+10;//这就是连续赋值,因为赋值符号是右结合性所以我们从右向左计算
复合赋值符就是
+= *= /= %= >>= <<= ....
很简单,a+=b相当于a=a+b;
单目操作符! //逻辑反操作 - //负值 + //正值 & //取地址 sizeof //操作数的类型长度(以字节为单位) ~ //对一个数的二进制按位取反 -- //前置、后置-- ++ //前置、后置++ * //间接访问操作符(解引用操作符) (类型) //强制类型转换
单目操作符是指只有一个操作数。
1.逻辑反,就是将真变为假,假变为真。
2.重点说sizeof,我们可以看到sizeof是操作符而并非函数,这里要区别与strlen,strlen是库函数。
int a =10; sizeof(a); sizeof a ;//这个计算变量的大小不加括号是可以运行的, 这也说明了sizeof不是函数,因为其不需要函数访问操作符。 sizeof(int); sizeof int ;//在我们的编译器中这个是跑不过的
另外还有一点,sizeof是不会压栈的这也说明了它不是函数。
关系操作符> >= < <= != 用于测试“不相等” == 用于测试“相等”
注意关系操作符‘==’只能比较整数,浮点数会有精度损失,字符串使用strcmp库函数
"abc" == "abcdef"这段代码比较的是两个字符串的地址
关系操作符还是比较简单的哈,这里需要着重看的是“ == “和”=“千万不要看错了!!!
&& 逻辑与 || 逻辑或
逻辑操作符要注意短路求值的问题,
逻辑与,如果第一个表达式为假,那么第二个表达式就不会进行判断了,结果为假。
逻辑或,如果第一个表达式为真,那么第二个表达式也就不会判断了,结果为真。
看一下例题:
#includeint 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;
第二个答案为1 3 3 4;
你做对了吗?
(expr1)?(expr2):(expr3);
该表达式的意思是,表达式1成立则返回表达式2的值,表达式1不成立则返回表达式3的值。
逗号表达式,就是用逗号隔开的多个表达式。
逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
exp1, exp2, exp3, …expN
深入理解一下,既然,逗号表达式内的表达式都会执行,那么我么是不是可以用它来完成do while的任务呢
int a = 0;
do
{
printf("%d ",a);
a++;
}while(a<=100);
while(printf("%d ",a),a++,a<=100);
是不是很有趣呢?
下标引用操作符,函数调用,结构体成员访问1.下标引用操作符
[ ]
用来操作数组元素的操作符
操作数:一个数组名 + 一个索引值
int arr[10];//创建数组 arr[9] = 10;//实用下标引用操作符。 [ ]的两个操作数是arr和9。 这时我们想一想,加号也是操作符,那加号的操作数可以交换,那数组的可不可以呢? 9[arr] = 10;//答案是可以的!! //编译器并不会报错,只能用来引用不可以定义数组哦。
2.()函数调用操作符,他的操作数是数组名和(expr1,expr2),最少一个操作数,即数组名,多可以很多个。剩余的操作数就是传递给函数的参数。
3. 访问一个结构的成员
.//结构体.成员名 ->//结构体指针->成员名
下面我们来看一段代码了解这两个操作符
#includetypedef struct stu { char name[10]; int ID; }stu; void print(stu *ps) { printf("name = %sn ID = %d",ps->name,ps->ID);//此处为->操作符的使用 //printf("name = %sn ID = %d",(*ps).name,(*ps).ID) //注释掉的写法是一样的效果哦 } int main() { struct stu s1 ={ "zhang",2209}; printf("name = %sn ID = %d",s1.name,s1.ID);//这种就是.操作符的使用。 print(&s1); }
这里再使用结构体指针的时候我们还有一个问题,就是在结构体传参的时候,尽量使用地址传递,因为在结构体传参时参数所占内存过大会对栈帧的占用很大,增加了系统开销。
具体原因可以看
这个博客
1.隐式类型转换关于表达式求值我们这里要谈到三个概念,操作符的优先级和结合性以及是否控制求值顺序。
C的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升
简单来说也就是小于int的类型在进行表达式求值的时候会被转换为int类型进行计算
官方一点的解释为:表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特位直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
看一段代码来理解
#includeint main() { char a = 127; char b = 5; char c = a + b; printf("%d", c);//输出为-124 return 0; }
下面来解释一下原理,char类型的a和b在进行加法的时候转换为int类型的这是会发生整型提升。
2.整形提升整形提升有两种,有符号数,高位补上符号位
无符号数,高位补0,要注意整型提升后这个数本身时不变的。
所以a+b=132其原码为
0000 0000 0000 0000 0000 0000 1000 0100
将该二进制码存入char c时发生了截断,将1000 0100存入了c这时候要注意了最高位的1被看作是符号位,而系统自动认为该二进制码为补码。所以输出的时候就要转换为原码
1000 0100
1111 1100//原码,对应-124
下面这段代码可以更好的理解什么时候需要整型提升
char a=0; sizeof(a);//1 sizeof(-a);//4 sizeof(+a);//4
不管是,计算,比较,%d打印,只要在表达式中使用时就会发生整型提升。
3.算术转换(大小>=int)说完了小于int的类型下面来看一下,大于int的类型
例如:int + float 这种类型的运算时候就会将int转换为float在进行运算。
同理int + double也是如此。
关于表达式的求值顺序,相邻操作符优先级相同时就看操作符的求值顺序来决定计算顺序。下面这个表了解一下即可
简单举一个例子:
int c = 10 + 20 +30;这就是操作符优先级相同的时候,我们就要看+号的结合性来决定从左向右计算还是从右向左计算。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p1sNoAyO-1652183416398)(操作符优先级表.jpg)]



