入门C语言第四话:详解操作符

前言

 部分的操作符已经在前面写过了,想要了解的可以点击初始C语言:常见操作符与关键字
  这一篇我们主要讲与内存相关的操作符!下面我们来了解一下我们要了解的内容吧!
在这里插入图片描述

正文

话题引入

内存中数据的存储——二进制

  在(signed)int类型中大小为4个字节,1个字节是8个比特位,一个比特位是存数据的最小内存单元,也就是二进制的0/1,那四个字节就是32位二进制的数,并且最高位是符号位(因为是signed)。

源码,反码,补码的关系
正数

说明:源码,反码,补码相同。
举例1:

int a =1;

源码:00000000000000000000000000000001
反码:00000000000000000000000000000001
补码:00000000000000000000000000000001(在内存中实际存的数据)
注意:计算机并不是直接把正数的源码存进去了,而是把源码转换为补码存进去了。

负数

说明:遵循着运算逻辑——反码等于源码按位取反(符号位,也就是最高位不变,把源码中的0变成1,1变成0),补码等于反码加上1。
特殊:int的取值范围:-2147483648 ~2147483647
负数的最小值的存储为:10000000000000000000000000000000(这是补码,并且这是语法规定的)
因此我们可以这样记住负数与正数的存储形式:

在这里插入图片描述
我们可以看出,这是一个轮回,是不是很神奇?

举例2:

int a =-1;

源码:10000000000000000000000000000001
反码:1111111111111111111111111111111111110
补码:1111111111111111111111111111111111111
到这里我们已经知道内存的二进制是如何转化和进行存储了,接下来我们进入今天的课题吧!

一.移位操作符

说明:
1.移位操作符运算后不会影响操作数本身
2.移位操作符运算的范围为整数

1. 左移操作符

符号:<<(双箭头向左)
功能:将补码整体左移n位舍去,右边补0。

int a =1;
int b = a<<1;//这是将a的补码向左移动一位,

a的补码:00000000000000000000000000000001
b的补码:00000000000000000000000000000010
在这里插入图片描述

2. 右移操作符

符号:>>(双箭头向右)

1.算数右移

功能:将补码整体向右移n位,最左边补符号位
例子:

int a =-1;
int b = a>>1:

a的补码:11111111111111111111111111111111
b的补码:11111111111111111111111111111111
在这里插入图片描述

1.逻辑右移

功能:将补码整体向右移n位,最左边补0
在这里插入图片描述

一般右移都是算数右移(补符号位)

二.位操作符

说明:
1.移位操作符运算后不会影响操作数本身
2.移位操作符运算的范围为整数
举例:

int a =1;
int b =2;
int c =a&b;

1.按位与

符号:&
功能:将两个数的补码进行比对,如果相同位数的数都为1这个位数的结果为1,否则为0。
口诀:同1为1,其余为0。
a的补码:00000000000000000000000000000001
b的补码:00000000000000000000000000000010
c的补码:00000000000000000000000000000000
c的结果为0.

2.按位或

符号:|
功能:将两个数的补码进行比对,如果相同位数至少有一个1这个位数的结果为1,否则为0。
口诀:同0为0,其余为1。
a的补码:00000000000000000000000000000001
b的补码:00000000000000000000000000000010
c的补码:00000000000000000000000000000011

3.按位异或

说明:
1.支持交换律
2.一个数异或0为本身
3.相同数异或为0.
符号:^
功能:将两个数的补码进行比对,如果相同位数的数不同为1,否则为0。
口诀:同为0,异为1.
a的补码:00000000000000000000000000000001
b的补码:00000000000000000000000000000010
c的补码:00000000000000000000000000000011
例题:交换两个数,不用第三个变量。
方法1:这是我们平常用的,不符合题目要求。

int main()
{
	int tmp = 0;
	int a = 2;
	int b = 1;
	tmp = a;
	a = b;
	b = tmp;
	printf("%d %d", a, b);
	return 0;
}

方法2:

int main()
{
	int a = 2;
	int b = 1;
	a = a + b;
	b = a - b;
	a = a - b;
	printf("%d %d", a, b);
	return 0;
}

说明:在两个数较大的情况下,交换可能会出现数据溢出的现象。
方法3:
要看一下异或的规律哦!

int main()
{
   int a = 2;
   int b = 1;
    a =a^b;
    b =a^b;//相当于a^b^b-->a
    a =a^b; //相当于a^b^a-->b
	return 0;
}

说明:代码的可读性差,并且计算较为复杂,在实际工程中建议采用第一种写法。
例2:将二进制中1的个数打印出来。
方法一:

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

说明:不适用负数,只适用于正整数。
方法2:

int main()
{
	int count = 0;
	int b = 10;
	int i = 0;
	for (i = 0; i < 32; i++)
	{
		if (((b >> i) & 1) == 1)//判断是否为1用&右移是将b的二进制位数的值依次与1比较相同为1
		{
			count++;
		}
	}
	printf("%d", count);
	return 0;
}

说明:计算的比较多余,每次计算都得算32遍。
方法3:

int main()
{
	int a = -1;
	int count = 0;
	while (a)
	{
		count++;
		a = a & (a - 1);

	}
	printf("%d", count);
	return 0;
}

原理:每次减去1,实际上把较低位的1变成0,此时再按位与就消掉较低位的1,最后如果没有1的话,按位与就变成了0。
说明:这种方法比较好,但是比较难想到。

三.结构体成员操作符

1.实参访问

符号:.

struct BOOK
{
	char author[20];//编号0
	char name[20];//编号1
	int price;//编号2
};
int main()
{
	struct BOOK a = { "shunhua","c江湖",0 };//定义了一本书的作者,书名,价格
	printf("%s %s %d", a.author, a.name, a.price);//通过.进行访问
	return 0;
}

2.地址访问

符号:->(一个减号,一个大于号)

struct BOOK
{
	char author[20];
	char name[20];
	int price;
};
int main()
{
	struct BOOK a = { "shunhua","c江湖",0 };
	struct BOOK *p = &a;
	printf("%s %s %d\n", (*p).author, (*p).name, (*p).price);//指针解引用,就是实参了。
	printf("%s %s %d", p->author, p->name, p->price);
	//指针存的地址就像箭头,可以通过地址访问。
	return 0;
}

四.函数调用操作符

符号:()
说明:至少有一个操作数——函数名

void test()
{
	printf("abcdef");
}
int main()
{
	test();//专指的是调用函数的小括号。其它的可不是哦!
	return 0;
}

五.下标引用操作符

符号:[]
说明:本质实际上地址进行运算再解引用,两个操作数有一个为地址。
公式表示:*(操作数1+操作数2)

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };//这里的[10]为有10个成员
	int *p = arr;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(arr + i));
	}
	printf("\n");
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);//本质上为上面的数组。
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d ", i[arr]);//本质上为上面的数组。
	}
	return 0;
}

六.表达式求值

1.整形提升

说明:
 1.C的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
 2.意义:
1.表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
2.通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令
中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转
换为int或unsigned int,然后才能送入CPU去执行运算

 3.截断
说明:将内存较大的数据类型转化为较为低的数据类型时,需要将二进制数据保留为低数据的内存的二进制数据大小。比如:int(32)位要转化为char(8)位。
例一:

int main()
{
	char a = 128;
	//发生截断存在char里面的是:(补码)10000000(-128)
	//运算时再整形提升:(signed)高位补符号位(补码)11111111111111111111111110000000
	char b = 2;
	char c = a + b;//-128+2
	printf("%d", c);
	return 0;
}

例二:

int main()
{
 char c = 1;
  printf("%d\n", sizeof(c=c+1));//将c赋为c+1结果的类型为char
 printf("%d\n", sizeof(c+1));//将c进行整形提升int
 printf("%d\n", sizeof(+c));//将c进行整形提升int
 printf("%d\n", sizeof(-c));//同理
 return 0;
}

2.算数转换

说明:
1.将精度小的(如:int)和精度大的(如:double)互相转换,但是可能会存在数据/精度丢失。
2.分为隐式转换和显式转换
3.类型大于等于int才算
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。

例:

int main()
{
	int a = 128;
	char b = 2;
	float c = a + b;//隐式转化
	c =(int)a+(int)b;//显式转换
	printf("%f", c);
	return 0;
}

3.操作数的计算顺序与优先级

在这里插入图片描述

这是我找的比较全的!

4.问题表达式

表达式的影响要素:
前提:相邻操作符

  1. 操作符的优先级
  2. 操作符的结合性
  3. 是否控制求值顺序。
    因此:在多表达式的计算中,相邻的表达式的优先级并不一定会知道,所以要写能看出结合性与优先性明了的式子。
    例一:
#include<stdio.h>
int main()
{
	int b = 0;
	int i = 1;
	b = (++i) + (++i) + (++i);
	printf("%d", b);
	return 0;
}

在vs编译器上是12,但是在Linux上可不一定哦!
运算的逻辑:先计算(++i)的部分,最后计算加起来的部分。
表达式的通病:优先级结合性求值顺序相互影响,又会影响最后的值。

补充:++a+ b * ++b + c,也算问题表达式,因为++b是先算的,但是前面的b不知道在什么时候算,是++b之后,还是之前都是有可能的。

尾序

如果能认真看到这里,我坚信你能收获很多很多!也希望这篇文章能帮助到你,如果觉得不错,请点击一下不要钱的赞,如果有误请温柔的指出,在这里感谢大家了

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值