操作符详解

一 ,操作符的分类

1.算术操作符:+ ,-, * ,/ ,%

2.移位操作符:<<     >>

3.位操作符    : &   |     ^   ~

4.赋值操作符:  =  ,+= ,-= ,*= ,/= ,%= ,<<= , >>= ,&= ,|= ,^=

5.单目操作符:! , ++ , --,  -  ,  +  , * ,  &  ,  ~  ,sizeof(类型)

6.关系操作符:>  , <  , >=  ,<=  , ==  , !=

7.逻辑操作符:&&    ||   !

8.条件操作符:?   : 

9.逗号表达式:  ,

10.下标引用 : [  ]

11.函数调用 : (   )

12.结构成员访问:  .     ,  -> 

二 ,二级制和进制转化

 在日常生活中,我们常常听到不同的进制,时钟有60进制,12进制,每周的7进制。其实无论是多少进制,都是数值的不同表示方法:

比方说:数值15的各种进制表现形式:

15的2进制:1111

15的8进制:17

15的10进制:15

15的16进制:F

// 16 进制的数值之前写: 0x

//  8进制的数值之前写   :0

以我们日常生活中常见的10进制做类比:

  • 10进制满10进1
  • 10进制的每一位数值是0-9组成的

推理到2进制:

  • 2进制满2进1
  • 2进制的每一位数值都是0-1组成的 

 2.1 二进制转十进制

在10进制中,520表示的是五百二十,是因为10进制中的每一位数都是有权重的,这里的520=0*10^0  +  2*10^1  + 5*10^2;

这与二进制也是类似的,只不过权重不同:

这里以二进制1101来举例:

 

2.2 10进制转二进制 

2.3 2进制转8进制

8进制的数字每一位都是0-7的,然后0-7的数字写成2进制,最多需要3位二进制数就足够了,比方说7的二进制数是111,所以在2进制数转成8进制数的时候,从最右边的最低为开始选3个数为一组来计算即可,如果不够3位,前补0或者直接换算; 

 2.4 2进制数转16进制数

16进制的每一位是0-9,a-f,然后把它们写成二进制的数,最多需要4位二进制数即可;

注意16进制表示时,前面加0x,所以二进制数 01101011 的16进制形式为  0x6b;

三 ,原码,补码,反码

 整数中的2进制表示方法有三种,即原码,反码,补码

有符号整数的三种表示方法均有符号位和数值为两部分,2进制序列中,最高位的1位被当作符号位,其余的都是数值为。

符号位中以0表示“正”,以1表示”负“;

 

四 , 移位操作符

<<      左移操作符

>>      右移操作符

注意: 操作数只能是整数

 4.1 左移操作符

移位规制:左边抛弃,右边补0  ---->进行运算的是数值的二进制(内存中的补码)

正数: 

int main()
{
	int n = 10;
	int m = n << 1;
	printf("%d\n", n);
	printf("%d\n", m);
	return 0;
}

 

负数:

int main()
{
	int a = -10;
	int b = a << 1;
	int c = a << 2;
	//10000000 00000000 00000000 00001010
	//补码:
	//11111111 11111111 11111111 11110110
	//左移:
	//11111111 11111111 11111111 11101100 --- b在内存中的补码
	//10000000 00000000 00000000 00010100
	printf("%d\n", a);
	printf("%d\n", b);
	printf("%d\n", c);
	return 0;
}

我们不难发现:左移运算符在一定程度上(并不是都是),每次左移一位,数值会在原有的基础上乘以2; 

需要注意的是:负数要转化成补码进行移动,最后在转化原码得到移动结果。

 

4.2 右移操作符

移动规则: 右移运算分为两种

1.逻辑右移:左边用0填充,右边丢弃

2.算数右移:左边用原来该数值的符号位填充,右边丢弃

右移是逻辑右移还是算数右移是取决于编译器的,但大多数的编译器采用的是算数右移。 

 

int main()
{
	int a = -10;
	int b = a >> 1;
	int c = a >> 2;
	int d = a >> 3;
	//10000000 00000000 00000000 00001010
	//11111111 11111111 11111111 11110110  --- 补码
	//11111111 11111111 11111111 11111011  
	//10000000 00000000 00000000 00000101 -- -5
	//右移到底是算数右移还是逻辑右移,是取决于编译器,但是大部分编译其采用的都是算术右移
	printf("%d\n", a);
	printf("%d\n", b);
	printf("%d\n", c);
	printf("%d\n", d);

	return 0;
}

 注意:对于移位符号位,不要移动负数位,这个标准是未定义的。

五 ,位操作符

&      按位与

 |       按位或

 ^      按位异或

 ~      按位非

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

int main()
{
	int a = -5;
	int b = 13;
	int c = a & b;
	int d = a | b;
	int e = a ^ b;
	int f = ~a;

	//a:10000000 00000000 00000000 00000101
	//a:11111111 11111111 11111111 11111011  -5补码
	//b:00000000 00000000 00000000 00001101  13补码
	//  00000000 00000000 00000000 00001001    & 9
	//  11111111 11111111 11111111 11111111    | 
	//  10000000 00000000 00000000 00000001
	//  11111111 11111111 11111111 11110110     ^
	//  10000000 00000000 00000000 00001010
	//  00000000 00000000 00000000 00000100     ~
	printf("%d\n", c);
	printf("%d\n", d);
	printf("%d\n", e);
	printf("%d\n", f);

	return 0;
}

 试着完成:

题目:不能创建临时变量(第三个变量),从而实现两个整数交换;

如果我们使用第三个变量就很容易实现,就像现在你有一瓶酱油和一瓶醋,我们可以通过一个空瓶子把这两个容器里的液体交换;

int main()
{
	int a = 10;
	int b = 20;
	int c = 0;
	printf("交换前:%d %d\n", a, b);
	c = a;
	a = b;
	b = c;
	printf("交换后:%d %d\n", a, b);
	return 0;
}

但是题目要求不可以用第三个变量,但我们可以先把两个数相加,然后赋值给a,然后再用赋值后的a减去b  实则上就是我们原来的a值,然后把这个值赋给 b ,再用a值减去 b ,就是我们原来的 b 值,然后赋值给a ,即可实现交换;

int main()
{
	int a = 10;
	int b = 20;
	printf("交换前:%d %d\n", a, b);
	a = a + b;
	b = a - b;
	a = a - b;
	printf("交换后:%d %d\n", a, b);
	return 0;
}

 这样计算,当数值较小的时候的确可以实现我们的数值交换,但是当数值越来越大的时候,两个数值进位相加,可能会超出整数表达的最大值,可能会存在溢出问题!

所以我们再次优化这个算法:

a^a   == 0

a^0   ==a 

异或:相异为1,相同为0

两个二进制数没有进位运算,不会导致溢出!

int main()
{
	int a = 10;
	int b = 20;
	printf("交换前:a=%d b=%d\n", a, b);
	a = a ^ b;
	b = a ^ b;  //a ^ b^ b; ----> a
	a = a ^ b; //a ^ b ^ a ---->b
	printf("交换后:a=%d b=%d\n", a, b);
	return 0;
}

 练习1:

编写代码实现:求⼀个整数存储在内存中的⼆进制中1的个数;
思路:在10进制中,我们 %10取余,得到某一个数值的最后一位数; /10取整得到某一个数值去掉最后一位数的数值;
int main()
{
	int n = 0;
	scanf("%d", &n);
	int count = 0;
	while (n)
	{
		if (n % 2 == 1) {
			count++;
		}
		n /= 2;
	}
	printf("二进制中1的个数为%d\n", count);
	return 0;
}

但是我们很快就可以发现,针对负数是有问题的;

然后我们,就会想,什么运算会不丢失原有而二进制的数值,然后又可以方便计算的,最后,我们可以想到按位与(&),让每一个二进制数按位与1

int main()
{
	int n = 0;
	scanf("%d", &n);
	int count = 0;
	for (int i = 0; i < 32; i++)
	{
		if ((1 & (n >> i)) == 1)
		{
			count++;
		}
	}
	printf("%d\n", count);
	return 0;
}

 但是这里我们计算了32次,然后我们思考,能不能再优化一下:

int main()
{
	int n = 0;
	scanf("%d", &n);
	int count = 0;
	while (n)
	{
		n = n & (n - 1);
		count++;
	}
	printf("%d\n", count);
	return 0;
}

 练习二:

题目:写一个代码判断 n 是否是2的次方数

2次方数有:1   2   4   8   16   32   64   .....

我们把他们转成二进制数:

00000001

00000010

00000100

00001000

00010000

................

我们发现他们有且仅有一个1 

int main()
{
	int n = 0;
	scanf("%d", &n);
	if ((n & (n - 1)) == 0)
	{
		printf("Yes!\n");
	}
	else
	{
		printf("No!\n");
	}
	return 0;
}

 案例3:

题目:编写代码将13⼆进制序列的第5位修改为1,然后再改回0

13 2 进制序列: 00000000000000000000000000001101
将第 5 位置为 1 后: 00000000000000000000000000011101
将第 5 位再置为 0 00000000000000000000000000001101
int main()
{
	int n = 13;
	n |= (1 << (5 - 1));
	//00000000 00000000 00000000 00001101
	printf("%d\n", n);

	//00000000 00000000 00000000 00001101   --13
	//00000000 00000000 00000000 00010000    |
	// 1 <<4
	//00000000 00000000 00000000 00011101
	//11111111 11111111 11111111 11101111    &
	//00000000 00000000 00000000 00001101
	n &= (~(1 << (5 - 1)));
	printf("%d\n", n);

	return 0;
}

 

六 ,单目操作符

 !、++--&*+-~ sizeof(类型)

 

七 , 逗号表达式

1.以逗号依次隔开的多个表达式

2.从左向右依次执行,整个表达式的结果是最后一个表达式的结果

//代码1
int a = 1;
int b = 2;
int c = (a>b, a=b+10, a, b=a+1);//逗号表达式
c是多少?
//代码2
if (a =b + 1, c=a / 2, d > 0)
//代码3
a = get_val();
count_val(a);
while (a > 0)
{
 //业务处理
 //...
 a = get_val();
 count_val(a);
}
如果使⽤逗号表达式,改写:
while (a = get_val(), count_val(a), a>0)
{
 //业务处理
}

 

八 , 下标访问[],函数调用()

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

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

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

九,操作符的属性:优先级,结合性

C语⾔的操作符有2个重要的属性:优先级、结合性,这两个属性决定了表达式求值的计算顺序。  

优先级指的是,如果⼀个表达式包含多个运算符,哪个运算符应该优先执⾏。各种运算符的优先级是不⼀样的。 如果两个运算符优先级相同,优先级没办法确定先计算哪个了,这时候就看结合性了,则根据运算符 是左结合,还是右结合,决定执⾏顺序。⼤部分运算符是左结合(从左到右执⾏),少数运算符是右 结合(从右到左执⾏)

 

十, 表达式求值

1.整型提升:

C语⾔中整型算术运算总是⾄少以缺省(默认)整型类型的精度来进⾏的。为了获得这个精度,表达式中的字符和短整型操作数在使⽤之前被转换为普通整型,这种转换称为整型提升
int main()
{
	char a = 5;
	//00000000 00000000 00000000 00000101
	//00000101   截断
	char b = 126;
	//00000000 00000000 00000000 01111110
	//01111110
	char c = a + b;
	//00000101   --- 发生整型提升  --00000000 00000000 00000000 00000101
	//01111110   --- 发生整型提升  --00000000 00000000 00000000 01111110
	//char  --- signed char 
	//00000000 00000000 00000000 10000011  --- 截断 -- 10000011
	//当以%d 形式进行打印的时侯,打印的是有符号整数
	//又会对c发生整形提升
	//11111111 11111111 11111111 10000011 -- 内存中的补码
	//10000000 00000000 00000000 01111101
	printf("%d\n", c);
	return 0;
}

如何进⾏整体提升呢?

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

2.算数转换:

如果某个操作符的各个操作数属于不同的类型,那么除⾮其中⼀个操作数的转换为另⼀个操作数的类型,否则操作就⽆法进⾏。下⾯的层次体系称为寻常算术转换
long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作数的类型在上⾯这个列表中排名靠后,那么⾸先要转换为另外⼀个操作数的类型后执⾏运算

 

  • 20
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值