【09 操作符讲解】

目录

  1. 操作符的分类
  2. 二进制和进制转换
  3. 原码、反码、补码
  4. 移位操作符
  5. 位操作符: &、|、^、~
  6. 单目操作符
  7. 逗号表达式
  8. 下标访问[]、函数调用
  9. 结构成员访问操作符
  10. 操作符的属性: 优先级、结合性
  11. 表达式求值

1. 操作符的分类

  • 算术操作符: +、-、*、/、%
  • 移位操作符: << 、>>
  • 位操作符: & | ^
  • 赋值操作符: =、+=、-=、*=、/=、%=、<<=、>>=、&=、|=、^=
  • 单目操作符: !、++、–、&、*、+、-、~、sizeof、(类型)
  • 关系操作符: >、>=、<、<=、==、!=
  • 逻辑操作符: &&、||
  • 条件操作符: ? :
  • 逗号表达式: ,
  • 下标引用: [ ]
  • 函数调用: ()
  • 结构成员访问: . ->

上述操作符中有一些和二进制有关,所以先铺垫一下进制转换的意思

2. 二进制和进制转换

各个进制都是数据的不同表示形式,比如15:

2进制: 1111
8进制: 17
10进制: 15
16进制: F

10进制是经常使用的,满十进一,每一位都是0-9组成
2进制的则是满二进一,每一位都是0和1组成

2.1 2进制转10进制

十进制的123表示的是一百二十三,因为每一位都是有权重的,从右往左分别是个位、十位、百位…分别求每一位的权重是100,101,102
在这里插入图片描述
二进制类似,只不过二进制从右到左是20,21,22
2进制的1101怎么理解:
2进制1101

2.2 10进制转2进制

在这里插入图片描述
依次除以2,求余数,直到商为0,最后从下到上排列余数,就是该数的二进制

2.3 2进制转8进制

8进制最大的数7写成二进是111,8进制只需要三个二进制就可表示,所以换算的时候直接从右往左每3个二进制换算一个8进制,剩余不够三个的直接换算
如: 2进制的01101011,换成8进制: 0153,0开头的数字,会被当做8进制
在这里插入图片描述

2.4 2进制转换为16进制

和8进制类似,16进制最大的F转换为2进制1111,只需要4个2进制就足够了,所以从右往左每4个一算
如: 2进制的01101011,换成16进制: 0x6b,0x开头是16进制
在这里插入图片描述

3. 原码、反码和补码

整数的二进制有三种形式:原码、反码、补码
无符号整数的所有位都是数据位
有符号整数的最高的第1位是符号位,1表示负,0表示正,
其中,正数的原码、反码和补码都是相同的
以1000 0000 0000 0000 0000 0000 0000 0001 ,因为第1位是1,所以是负的,最后有1,所以是-1,这就是原码
原码:直接按照正负数的形式翻译成二进制就是原码
反码: 原码的符号位不变,其他位依次取反
1111 1111 1111 1111 1111 1111 1111 1110
补码:反码+1
1111 1111 1111 1111 1111 1111 1111 1111

整数来说,数据存放的形式是补码

使用补码,可以将符号位和数据位统一处理,加法和减法也统一处理(CPU)只有加法器,减法实际转换为加法,如果用原码计算,会发现结果是错误的。但使用补码计算无误。此外,补码和原码之间转换的形式相同,即取反+1,不需要额外的硬件电路

在这里插入图片描述

4. 移位操作符

<< 左移操作符

右移操作符
注: 移位操作符的操作数只能是整数

4.1 左移操作符

移位规则: 左边抛弃,右边补0
以10举例
在这里插入图片描述
结果换算为十进制是20
vs验证:

int num = 10;
int n = num << 1;
printf("n= %d\n", n);
printf("num= %d\n", num);

在这里插入图片描述

4.2 右移操作符

移位规则:
1.逻辑右移: 左边用0填充,右边丢弃
2.算术右移,左边用原该值符号位填充,右边丢弃
如果是负数,第1位是符号位,用0填充会改变原值的正负,很明显不合适,所以采用的是算术右移
在这里插入图片描述
-1举例,-1的补码右移一位,左边补符号位,右边丢弃,值还为-1
vs验证:

int num = -1;
int n = num >> 1;
printf("n= %d\n", n);
printf("num= %d\n", num);

在这里插入图片描述

警告: 不能移动负数位,这个标准未定义

5. 位操作符

& //按位与
| //按位或
^ //按位异或
~ //按位取反

注: 它们的操作数必须是整数
& : 只有两个操作数的对应位都是1时结果才为1
|: 如果其中任意操作数中对应的位为1,那么结果位就为1.
^: 有一个1是1,都为0或1结果为0
~: 1变为0,0变为1

代码如下:

int num1 = -3;
int num2 = 5;
printf("%d\n", num1 & num2);
printf("%d\n", num1 | num2);
printf("%d\n", num1 ^ num2);
printf("%d\n", ~0);

在这里插入图片描述
如上图,第一个框求出-3和5的补码进行计算,第二个框是计算过程,红色表示最终翻译成原码的结果。第三个框是对0取反的结果,运行验证
在这里插入图片描述

5.1 交换两数

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

第一种方法:
思路: 先保存a+b的和,然后减去一个赋值给另一个,最后减去求得最后一个

int a = 3;
int b = 5;
a = a + b;
b = a - b;
a = a - b;
printf("a=%d,b=%d", a, b);

这种方法的问题在,如果a和b的和太大,超出了int可以存储的范围就会产生错误

第二种方法:
首先存在一个规律:
两个同样的数异或结果为0,a^a=0
0异或任何数结果仍为该数,0^a=a
思路: 利用上面两个规则,通过保存a^b取得每个数

a = a ^ b;
b = a ^ b;  //a^b^b  原来的a赋值给b
a = a ^ b;  //a ^ b^a  b的值现在是原来的a,结果是原来的b

执行验证:
在这里插入图片描述

5.2 编写代码求整数内存中二进制1的个数

第一种方法:
思路: 10进制中,一个数%10可以得到个位数字的值,然后/10可以去掉最末位数字。那么二进制也可以通过%2得到末位的值判断

int num = 10;
int count = 0;
while (num) {
	if (num % 2 == 1)
		count++;
	num /= 2;
}
printf("有%d个1",count);

这种方法的问题在于,如果不够除以2的时候就会计算为0,结束循环。例如num=-1时,计算为0

第二种方法:
思路: &的计算是该位两个都是1则补1,1<<1位结果是2进制10,<<2结果是2进制100,和一个数字&运算可以得知该位是不是1

int num = 1;
int count = 0;
for (int i = 0; i < 32; i++) {
	if (num & (1 << i))
		count++;
}
printf("有%d个1",count);

上述方法必须循环32次,有什么更优的方法呢?

第三种方法:
num & (num-1) 的效果可以每次将一个数的二进制最后一位1去除
在这里插入图片描述

int num = 5;
int count = 0;

while (num) {
	count++;
	num = num & (num - 1);
}
printf("有%d个1", count);

相似的,判断一个数是不是2的次方
观察2的次方数发现,它们二进制都只有一个0
所以用num & (num-1)==0则是2的次方数

5.3 二进制某位置0或者1

将下列13二进制第5位修改为1,然后再变回0

13的2进制序列: 00000000000000000000000000001101
将第5位置为1后:00000000000000000000000000011101
将第5位再置为0:00000000000000000000000000001101

int num = 13;
num = num | (1 << 4);
printf("%d", num);
num = num & ~(1 << 4);
printf("%d", num);

或运算符,只要有1便为1。和1与,该位是什么依旧是什么,和0与都是0

二进制的操作在单片机和嵌入式应用很多

6. 单目操作符

!、++、–、&、*、+、-、~、sizeof、(类型)
单目操作符的特点是只有一个操作数,& 和 *在学习指针的时候再介绍

7. 逗号表达式

exp1,exp2,exp3

逗号表达式,就是用逗号隔开的多个表达式
从左向右执行,结果是最后一个表达式的结果

int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1);
printf("%d", c);

可以简化代码:

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

while (a = get_val(), count_val(a), a > 0) {

}

8. 下标访问[]和函数调用()

下标访问: 一个数组名+一个索引值

arr[9]=10;

函数调用操作符: 第一个操作数是函数名,剩余操作数是传递给函数的参数

9. 结构成员访问操作符

c语言提供了很多内置类型,但仅有这些类型是不够的。描述一本书,需要作者、出版社、定价等,单一的数据不够,所以增加了结构体这种自定义的数据类型
📌结构体是一些值的集合,每个成员可以是不同类型的成员变量

9.1 结构体的声明

struct tag
{
member_list;
}varible_list

struct-关键字
tag-结构体名
member-成员变量列表
varible_list-实例化列表

描述一个学生

struct Stu
{
char name[20]; //名字
int age; //年龄
char sex[5]; //性别
char id[20]; //学号
}; //分号不能丢

9.2 结构体变量的定义和初始化

定义

//定义时给出	定义
struct Point {
	int x;
	int y;
}p1;
//创建时定义
struct Point p2;

初始化

//初始化
struct Point p3 = {20,15};         //顺序初始化
struct Point p4 = {.x=14,.y=30};   //指定初始化

嵌套初始化

//嵌套结构体初始化
struct Node {
	int data;
	struct Point p5;
};

struct Node n1 = { 23,{34,40} };

结构体成员访问操作符
可以用"."来访问,结构体变量.成员名

struct Node {
	int data;
	struct Point p5;
};

struct Node n1 = { 23,{34,40} };
printf("%d %d %d", n1.data, n1.p5.x, n1.p5.y);

结构体成员的间接访问
定义一个指向结构体变量的指针,间接访问,结构体指针->成员名

struct Node {
	int data;
	struct Point p5;
};

struct Node n1 = { 23,{34,40} };
struct Node* pt = &n1;
pt->data = 20;

printf("%d %d %d", pt->data, pt->p5.x, pt->p5.y);

10. 操作符的属性: 优先级、结合性

10.1 优先级

如果一个表达式包含多个运算符,哪个运算符优先执行。各种运算符的优先级是不一样的

3+4*5

表达式里既有加法运算,也有乘法运算,由于乘法的优先级高于加法,所以先计算4*5,而不是先计算3+4

10.2 结合性

如果两个优先级相同,没办法确定先算哪个,这时候就看结合性了,则根据是左结合还是右结合,决定执行顺序,大部分是从左到右,少数从右到左

5*6/2

因为乘法和除法的优先级相同,所以从左到右执行,先5*6,然后再除

运算符的优先级过多,大概记住下面几个就行了,其他的可查看下面表格

  • 圆括号 ()
  • 自增运算符 ++, 自减运算符 –
  • 单目运算符 +和-
  • 乘法 * , 除法 /
  • 加法 + , 减法 -
  • 关系运算符 <、>
  • 赋值运算符 =

由于圆括号的优先级最高,可以使用它改变运算符的优先级
在这里插入图片描述

11. 表达式求值

11.1 整形提升

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

表达式的整形运算要在CPU的相应运算器执行,整形运算器(ALU)操作字节数一般都是int的字节长度,同时也是CPU的通用寄存器的长度
因此,即使两个char类型的相加,在CPU内执行也要先转换为整形操作数的标准长度
通用CPU难以直接实现两个8比特字节直接相加运算。所以各种小于int长度的整形值,都必须先转为int或unsigned int,才能送入CPU内执行运算

//实例1
char a,b,c;

a=b+c;

b和c的值被提升为普通整形,然后再执行加法运算
加法运算完成后,结果截断,然后再存储在a中

如何进行整形提升

  1. 有符号整数提升按照变量的数据类型的符号位来提升
  2. 无符号整数提升,高位补0

负数的整形提升
char c1=-1;
变量c1的二进制(补码)中只有8个比特位:
1111 1111
char为有符号的,整形提升的时候高位补充符号位1,结果是:
1111 1111 1111 1111 1111 1111 1111 1111
//正数的整形提升
char c2=1;
char只有8个比特位:
0000 0001
整形提升补符号位0,结果是:
0000 0000 0000 0000 0000 0000 0000 0001

例:计算char类型的相加

char c1=125;
char c2=10;
char c3=c1+c2;
printf(“%d”,c3);
//一步步分析
char c1=125; 用0补完32位,因为char只存8为所以截断,结果:
0111 1101
char c2=10; 结果:
0000 1010
char c3=c1+c2;
//计算时整形提升计算
0000 0000 0000 0000 0000 0000 0111 1101 c1
0000 0000 0000 0000 0000 0000 0000 1010 c2
0000 0000 0000 0000 0000 0000 1000 0111 相加结果
c3为char,只能存8位
1000 0111
printf时整形提升
1111 1111 1111 1111 1111 1111 1000 0111 补码
1000 0000 0000 0000 0000 0000 0111 1000 反码
1000 0000 0000 0000 0000 0000 0111 1001 原码
截断 0111 1001 结果为121
由于是负的,所以最终打印-121

11.2 算术转换

如果某个操作符的各个操作数属于不同的操作类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作无法进行。称为寻常算术转换

long double
double
float
unsigned long int
long int
unsigned int
int

上面排名靠后的会先转换为靠前的计算

11.3 问题表达式解析

表达式1

ab+cd+e*f

由于的优先级较高,会先计算乘法,但只能保证前两个法先计算,无法保证第三个乘法也先计算。或者字母代表的是一个表达式,也可能会导致运算错误
正确的写法是:

ab
c
d
ab+cd
ef
a
b+cd+ef

表达式2

c + --c

只能保证–的优先级,但无法保证左边c的值获取是在右边–c之后还是之前,所以有歧义
表达式3

int i=10;
i=i-- - --i * (i=-3) * i++ + ++i
printf("i = %d\n", i);

以下是该表达式在各个环境的结果:
在这里插入图片描述

表达式4

int fun()
{
 static int count = 1;
 return ++count;
}

 int answer;
 answer = fun() - fun() * fun();
 printf( "%d\n", answer);//输出多少?

函数调用的先后顺序无法通过操作符优先级来确定
表达式5

 int i = 1;
 int ret = (++i) + (++i) + (++i);
 printf("%d\n", ret);
 printf("%d\n", i);

gcc编译结果:
在这里插入图片描述

vs编译结果:
在这里插入图片描述

第三个++和第一个+的先后顺序无法确定

11.4 总结

即使有了操作符的优先级和结合性,我们写的表达式依然有可能确定不了唯一计算路径,建议不要写出有歧义的表达式

  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值