C语言操作符(上)

操作符

1. 操作符的分类

操作符

操作符的分类想必大家都知道,这里就不赘述了。本文只是介绍其中的一部分操作符。

2. 算术操作符

算术操作符主要有:

+ - * / %

其中除法(/)分为两类:

  1. 只含整数的除法:运算结果为整数
  2. 含有浮点数的除法:运算结果为浮点数

注意:%(取模)的结果是余数。并且取模操作符的两个操作数必须都是整数

3. 移位操作符

<< 左移操作符

>>右移操作符

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

【右移】

补充:整数的二进制表示形式有三种:原码反码补码

  1. 正数的原码反码补码相同。
  2. 负数的原码反码补码要进行计算,不能直接判断。

原码反码补码的计算方法:一个整数,无论正负,直接将其翻译为二进制序列,该二进制序列就是原码。

int a = 15 ;

因为15是被存放在int型中的,而int型有32个比特位,其最高位表示符号位,将其翻译成二进制位就是:

00000000000000000000000000001111  //(最高位是0,表示该数为正数)

就得到了a的原码。因为a是15,为正数,所以该原码同时也是a的反码和补码。

int b = -15;

接下来我们将b翻译成二进制序列:

10000000000000000000000000001111  //原码    (最高位是1,表示该数是负数)
11111111111111111111111111110000  //反码    符号位不变,其他位按位取反得到b的反码
11111111111111111111111111110001  //补码    反码+1得到补码

该二进制序列是b的原码,由于b是负数,所以其反码和补码要根据上面的规则进行计算。

注意:数据在内存中是以补码的形式进行存储的

那么移位操作就是对数据的补码进行操作的。例如:

int a = 15;
00000000000000000000000000001111	//a的原码 同时也是反码和补码,这里我们将其看作补码
int b = a >> 1;	//将a右移一位
00000000000000000000000000000111	//a右移之后得到的二进制序列,得到的是补码,但其是正数,所以其原反补相同
printf("%d",b);		//打印7
printf("%d",a);		//a的值不变,还是15

注意:C语言中的右移操作分位两种:(一般编译器采用的是算术右移

  • 算术右移(右边丢弃,最高位补原来的符号位)
  • 逻辑右移(有边丢弃,最高位一律补0)

在内存中存放的是补码,那么操作之后得到的结果也应该是补码,所以我们要想知道操作的值是多少,必须将操作结果转换成原码,这里看到代码第4行,其最高位是0,代表运算结果是负数,所以其原码反码补码相同。所以直接将该二进制序列的值赋值给b变量,b变量的值就是a右移两位的结果。

接下来在讲一个负数右移:

int main()
{
	int a = -15;
	10000000000000000000000000001111    //a的原码
    11111111111111111111111111110000	//原码除了符号位不变,其他按位取反得到反码。
    11111111111111111111111111110001	//反码+1得到补码
	int b = a >> 1;
    11111111111111111111111111111000	//右移一位,最高位补符号位,也就是补1,得到补码
    11111111111111111111111111110111	//除了符号位之外,其他按位取反,得到反码
    10000000000000000000000000001000	//反码+1,得到原码,翻译成10进制,也就是-8
	printf("%d\n", b);//打印 -8
	printf("%d\n", a);//打印 15
	return 0;
}

得到操作结果之后,再将该结果转换为其原码,再翻译为十进制,就是-8。所以执行结果是打印-8

【左移】:左移就是将数据的补码向左移动,左边被移出的数据直接丢弃,右边补0。得到的操作结果仍然位补码,需要将其转换成原码。举个例子:

int main()
{
	int a = 15;
	00000000000000000000000000001111    //a的原码,a为正数,原反补相同
	int b = a << 1;
    00000000000000000000000000011110	//左移一位得到补码,由符号位可知,该二进制序列为正数,原反补相同
	printf("%d ", b);//打印30
	printf("%d ", a);//打印15
	return 0;
}

特别注意:移位操作不能移动负数位,例如:

int a = 15 >> -1;

这种操作是错误的是C语言标准未定义的

4. 位操作符

这里要先知道位操作符都是针对数据的二进制补码进行运算的,所以位操作符的操作数必须是整数。

【分类】

  • &(按位与)
  • |(按位或)
  • ^(按位异或)

【按位与】

运算规则:二进制位有0则为0,全是1才为1。例如:

int main()
{
	int a = 2;
	//00000000000000000000000000000010
	int b = 1;
	//00000000000000000000000000000001
	int c = a & b;
	//00000000000000000000000000000000
	printf("%d ", c);	//打印0
	return 0;
}

这里分别写出a和b的二进制补码,将其进行按位与操作,得到全0的二进制序列,结果也就是0。

【按位或】

运算规则:二进制位有1则为1,全为0才是0。例如:

int main()
{
	int a = 2;
	//00000000000000000000000000000010
	int b = 1;
	//00000000000000000000000000000001
	int c = a | b;
	//00000000000000000000000000000011
	printf("%d ", c);	//打印3
	return 0;
}

分别写出a和b的二进制补码,将其进行按位或操作,得到的二进制序列翻译成10进制也就是3。

【按位异或】

运算规则:二进制位相同为0,相异为1。例如:

int main()
{
	int a = 5;
	//00000000000000000000000000000101
	int b = 1;
	//00000000000000000000000000000001
	int c = a ^ b;
	//00000000000000000000000000000100
	printf("%d ", c);	//打印4
	return 0;
}

分别写出a和b的二进制补码,将其进行按位异或操作,得到的二进制序列就是4。

【按位异或的性质】
  1. 一个数和0异或得到的结果仍是其本身:我们将一个整数a和0进行异或操作,得到的一定是a本身。
  2. 两个相同的数异或得到的结果一定是0。
  3. 异或运算满足交换律和结合律。
【按位异或的应用】

Q1:如何交换两个整数?

交换两个整数的值可以有三种方法:

【Answer1】

int main()
{
	int a = 10;
	int b = 20;
	printf("before: a = %d b = %d\n", a, b);
	int tmp = a;
	a = b;
	b = tmp;
	printf("after : a = %d b = %d\n", a, b);
	return 0;
}

这种方法会创建新的变量,但是总体问题不大。

【Answere2】

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

这种方法有个弊端:当a和b的值很大时,将其加在一起可能就会超过int数据类型所能存储的最大数据范围。

【Answer3】

int main()
{
	int a = 10;
	int b = 20;
	printf("before: a = %d b = %d\n", a, b);
	a = a ^ b;
	b = a ^ b;
	a = a ^ b;
	printf("after : a = %d b = %d\n", a, b);
	return 0;
}

这种方法就比较巧妙了。

在这里插入图片描述

这里要结合异或运算的性质进行分析。

【位操作符的应用】

Q2:求一个整数存储在内存中的二进制中1的个数。

【Answer1】

int main()
{
    int num  = 10;
    int count=  0;//计数
 	while(num)
 {
 	if(num%2 == 1)
 		count++;
 	num = num/2;
 }
 	printf("二进制中1的个数 = %d\n", count);
 	return 0;
}

首先强调:**该方法只适用于正数!**该方法先判断该非0数是不是奇数,若是奇数,其最低的二进制位必定是1,计数器加1。接着再将该数除以2,(这里要注意将一个正整数除以2等效于将其二进制序列右移一位),再通判断该数的奇偶性间接判断其二进制位的最低位是否为1。知道num为0则停止。

该方法的缺陷是只适用于正整数

【Answer2】

我们首先知道,1与0进行按位与操作得到的是0,1与1按位与操作得到的1。

所以可以将该整数的二进制位的最低位和1进行按位与操作,其他高位与0进行按位与操作。此时整个表达式的执行结果就只与该整数的最低二进制位有关了,若最低二进制位是1,则结果为1,若最低二进制位为0,则结果为0。将该最低二进制位判断完毕之后,将整个二进制序列右移1位。将该操作进行32次,因为一个int类型的数据占用32个二进制位,我们需要将每一位都判断一次。

int main()
{
    int num = -1;
    int i = 0;
    int a = 1;
    int count = 0;//计数
    for (i = 0; i < 32; i++)
    {
        if (num & a)
            count++;
        num >>= 1;
    }
    printf("二进制中1的个数 = %d\n", count);
    return 0;
}

【Answer3】

int main()
{
     int num = 9;
     int i = 0;
     int count = 0;//计数
     while(num)
     {
         count++;
         num = num&(num-1);
         //第一次执行该语句时:
         //00000000000000000000000000001001
         //00000000000000000000000000001000
         //00000000000000000000000000001000
     }
     printf("二进制中1的个数 = %d\n",count);
     return 0;
}

这种方法难以想到。每执行一次num = num&(num-1);语句都能除去二进制序列中权值位最小的1。

5. 赋值操作符

赋值操作用于给变量初始化或者改变变量的值。

int a = 10;	//初始化
a = 20;		//赋值

赋值操作也能写作复合形式:

int a = a+1; 写做int a += 1;

int a = a>>1;写做 int a >>= 1;

……

6. 单目操作符

概念:只有一个操作数的操作符称之为单目操作符。

主要的单目操作符:逻辑反(!),取地址(&),操作符的类型长度(sizeof),强制类型转换(一对小括号)……

6.1 sizeof操作符

#include <stdio.h>
int main()
{
     int a = -10;
     int *p = NULL;
     printf("%d\n", !2);
     printf("%d\n", !0);
     a = -a;
     p = &a;
     printf("%d\n", sizeof(a));
     printf("%d\n", sizeof(int));
     printf("%d\n", sizeof a);//这样写行不行?
     printf("%d\n", sizeof int);//这样写行不行? 不可!会报错
     return 0;
}
//关于sizeof其实我们之前已经见过了,可以求变量(类型)所占空间的大小。

6.2 sizeof和数组

#include <stdio.h>
void test1(int arr[])
{
     printf("%d\n", sizeof(arr));//(2) 打印4/8
}
void test2(char ch[])
{
     printf("%d\n", sizeof(ch));//(4) 打印4/8
}
int main()
{
     int arr[10] = {0};
     char ch[10] = {0};
     printf("%d\n", sizeof(arr));//(1)  打印40
     printf("%d\n", sizeof(ch));//(3)   打印10
     test1(arr);
     test2(ch);
     return 0;
}

注意:

  1. 当sizeof的括号内是数组名是算出的是整个数组的字节大小。
  2. 数组传参传递的是数组首元素的地址。所以在test1函数和test2函数中计算的是指针变量的大小,而指针变量的大小在32位平台下占用的空间是4个字节,在64位平台占用的是8个字节。
  3. sizeof后的括号可以去掉就说明sizeof并不是函数。因为函数的调用必须要括号。
  4. 在数组中去掉名字,剩下的就是数组的类型。例如:数组int arr[10];的类型就是 int [10]

当scanf读取失败时,返回的时EOF,值是-1,-1的补码全为1,可以通过以下的代码实现多组输入。

int main()
{
	int a = 0;
    //假设返回EOF,其二进制序列全是1,取反之后全是0,条件为假,退出循环。
	while(~scanf("%d",&a))
	{
		printf("%d ",a);
	}
	return 0;
}

7. 关系操作符

关系操作符较为简单。

8.逻辑操作符

主要有两个 && (与)和 ||(或)。

注意:这里要与按位与和按位或做区分。

1&2----->0
1&&2---->1

1|2----->3
1||2---->1

先看一道题:

int main()
{
    int i = 0,a=0,b=2,c =3,d=4;
    i = a++ && ++b && d++;
    //i = a++||++b||d++;
    printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
    return 0;
}

执行结果:

在这里插入图片描述

分析:代码第4行a++ 表达式的结果是0,也就是假,对于逻辑与来讲,只要遇到执行结果为假的表达式,该语句之后的代码则不会再执行了。

逻辑与和逻辑或的特点:逻辑与和逻辑或会发生“短路现象”。

对于逻辑与来讲,倘若遇到执行结果为假的表达式,则该语句之后的表达式就不会再继续执行了。

对于逻辑或来讲,倘若遇到执行结果为真的表达式,则该语句之后的表达式就不会再继续执行了。

9. 条件操作符

exp1 ? exp2 : exp3

条件操作符与 if else语句可以相互转换。

Q3

if (a > 5)
        b = 3;
else
        b = -3;
转换成条件表达式,是什么样? //  b = (a>5)?3:-3;

10. 完结

本章的内容就到这里啦,若有不足,欢迎评论区指正,最后,希望大佬们多多三连吧,下期见!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

这里是彪彪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值