操作符详解

写在前面

这里的内容虽然有些多,不过整体较为简单,我总结了一些相对有些难度的的知识点。

操作符

C语言的操作符有很种,这里我把常见的一些和大家进行分析一下.

  • 算术操作符
  • 移位操作符
  • 位操作符
  • 赋值操作符

算术操作符

所谓的算数操作符就是我们的加减乘除,没有什么可以谈的.

+ - * / %

我在这里就谈谈 % 这个操作符,这个操作符是我们取余数的操作,有点意思.

int main()
{
	int num = 11;
	int ret = num % 10;
	printf("%d\n", ret);
	return 0;
}

操作符详解_第1张图片

注意,只有整数可以取余.

int main()
{
	double num = 11.0;
	double ret = num % 10;
	printf("%lf\n", ret);
	return 0;
}

操作符详解_第2张图片

移位操作符

在谈下面几个操作符时,我先说一下数据在内存中的存储形式,我们知道数据可以用三种二进制方式进行表示,其中补码是计算机的存储方式,毕竟我们数据存在负数,关于原反补三个是如何计算的,我们这里不谈.移位操作符分为下面的两种.

  • 左移
  • 右移

左移

注意左移操作符不是向左移动,准确的说应该是向高位移动,只不过我们平常喜欢把高位放在左边,故我们称之为左移.那么请问当我们左移之后,要知道类型的比特位数是固定的,那么最右边的空位我们因该如何做?这空缺位补0,某种意义上移动一位相当于数据翻倍.

操作符详解_第3张图片

#include

int main()
{
	int a = 5;
	int b = a << 1;
	printf("%d\n", b);
	printf("%d\n", a);
	return 0;
}

操作符详解_第4张图片

右移

右移是向低位移动.右移分为逻辑右移和算术右移.

  • 逻辑右移 二进制向右移动 空缺位补 0
  • 算术右移 二进制向右移动 空缺位补 符号位

对于正数来说,右移时逻辑右移还是算术右移都一样,但是负数就不一样了,

操作符详解_第5张图片

大多数编译器支持算术右移,下面我们看看VS系列怎么样

#include

int main()
{
	int a = -5;
	int b = a >> 1;
	printf("%d\n", b);
	printf("%d\n", a);
	return 0;
}

操作符详解_第6张图片

位操作符

位操作符能够直接改变数字的二进制,下面我就以两个例题来总结位操作符的用法,注意,他们的操作数必须是整数。

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

如何判断一个数是不是2的n次幂,我们看看下面数的特点
操作符详解_第7张图片

你会发现,凡是2的n次幂的数 ,他的二进制一定只有一个数字为 1 (这里不考虑负数),那么这就带来一种思路,我们是否可以通过位操作符进行判断,

#include

int main()
{
	int n = 0;
	scanf("%d", &n);
    // n-1 一定会拿掉那个唯一的 1 的
	if ((n & (n - 1)) == 0)
	{
		printf("YES\n");
	}
	return 0;
}

操作符详解_第8张图片

交换a,b,不出现第三个变量,这里直接就给思路了 我们发现 n ^ n ==0 , n ^ 0 == n
看下面代码

#include

int main()
{
	int a = 10;
	int b = 20;
	printf("Before a = %d b = %d\n", a, b);

	a = a ^ b;
	b = a ^ b;     //把 b 赋给 a
	a = a ^ b;     //把 a 赋给 b
	printf("After  a = %d b = %d\n", a, b);
	return 0;
}

操作符详解_第9张图片

赋值操作符

这里的赋值操作符就是 = 比较简单,这里就提一下 允许连续赋值

#include

int main()
{
	int a = 0;
	int b = 0;
	a = b = 5; //我们不建议 代码风格不好
	printf("%d %d", a, b);
	return  0;
}

操作符优先级

注意,操作符的优先级是会影响我们的我们代码的,如果操作符的优先级记忆错了那么有可能会带来很大的影响.不过我们不谈,与其我们背诵操作符的优先级不如我们多多使用括号,这样逻辑还清晰,而且不容易出错.

取整方式

我们都知道 5 / 2 = 2,这是我们记住的,那么我们想过没有,为什么结果是2呢?有人可能这样想在数学中 5 / 2 = 2.5,结果是要求是整型,去尾,所以是2.那为什么不是四舍五入得到 3 呢?这是编译器不同的取整方式造成的.下面我们谈谈取整的几种方式.

零向取整

零向取整 , 本质是向0靠近,不一定是四舍五入,否则-2应该是-3.

操作符详解_第10张图片

#include

int main()
{
	int a = 2.9;
	int b = -2.9;

	printf("%d\n", a);
	printf("%d\n", b);
	return 0;
}

操作符详解_第11张图片

这也解决了 为何5 / 2 结果是2的问题,C语言默认采用零向取整.这里有一个函数,支持零向取整.

#include
#include

int main()
{
	printf("%d\n", (int)trunc(2.9));
	printf("%d\n", (int)trunc(-2.9));
	return 0;
}

操作符详解_第12张图片

地板取整

所谓的地板取整就是向负无穷取整.

操作符详解_第13张图片

如果我想要它像负无穷取整的话.这里也是通过一个函数来实现.

操作符详解_第14张图片

#include 
#include  //因为使用了floor函数,需要添加该头文件

int main()
{
	//本质是向-∞取整,注意输出格式要不然看不到结果
	printf("%.1f\n", floor(-2.9)); //-3
	printf("%.1f\n", floor(-2.1)); //-3
	printf("%.1f\n", floor(2.9)); //2
	printf("%.1f\n", floor(2.1)); //2

	return 0;
}

操作符详解_第15张图片

向+∞取整

这个和上面恰好相反.
操作符详解_第16张图片

相关的函数如下.

操作符详解_第17张图片

#include 
#include 

int main()
{
	//本质是向+∞取整,注意输出格式要不然看不到结果
	printf("%.1f\n", ceil(-2.9)); //-2
	printf("%.1f\n", ceil(-2.1)); //-2
	printf("%.1f\n", ceil(2.9)); //3
	printf("%.1f\n", ceil(2.1)); //3

	return 0;
}

操作符详解_第18张图片

四舍五入取整

我们来到了我们最熟悉的的取整方式了.这里是round函数.

#include 
#include 

int main()
{
	//本质是四舍五入
	printf("%.1f\n", round(2.1));
	printf("%.1f\n", round(2.9));
	printf("%.1f\n", round(-2.1));
	printf("%.1f\n", round(-2.9));

	return 0;
}

操作符详解_第19张图片

取模

下面我们说一下是什么是取模?

如果a和d是两个自然数,d非零,可以证明存在两个唯一的整数 q 和 r,满足 a = q*d + r 且0 ≤ r < d.其中,q被称为商,r 被称为余数.

正数取模

这里很简单,我们一眼就可以看出了,重点放在负数上面,他们的一些总结是可以用在这里的.

int main()
{
	int a = 10;
	int d = 3;
	printf("%d\n", a % d);
	return 0;
}

操作符详解_第20张图片

负数取模

这里才是我们的大头,我们试试不同的编译环境然后仔细分析一下我们该如何做.

#include 
#include 

int main()
{
	int a = -10;
	int d = 3;
	//printf("%d\n", a/d); //C语言中是-3,很好理解
	printf("%d\n", a % d);
	
	return 0;
}

我们把这个代码在不同的环境下跑一遍,先看现象,再来和大家分析.

操作符详解_第21张图片

我们不是说余数大于0吗?是不是定义错了.实际上因为在C语言中,现在-10%3出现了负数,根据定义:满足 a = q*d + r 且0 ≤ r < d,C语言中的余数,是不满足定义的,因为,r<0了.故,大家对取模有了一个修订版的定义:

如果a和d是两个自然数,d非零,可以证明存在两个唯一的整数 q 和 r,满足 a = q*d + r , q 为整数,且0 ≤ |r|< |d|.其中,q 被称为商,r 被称为余数.

那么在Python中呢?这里的结果是什么样的呢?

操作符详解_第22张图片

这里我们就疑惑,好像出现了矛盾,实际上不是的,这是我们不同语言默认的取整的方式不同, Python采用向-∞取整

操作符详解_第23张图片

这里我们给出一个结论,方式决定商,商决定余数

下面我们继续说一下,我们有的时候把取模也称之为取余,这里还是有一定的区别的.

  • 取余:尽可能让商,进行向0取整.
  • 取模:尽可能让商,向-∞方向取整

提升与截断

我们之前谈到变量的创建是要在内存中开辟空间的。空间的大小是根据不同的类型而决定的。可是我们看一下下面的代码.

int main()
{
	int a = 10;
	char ch = 'a';
	int x = ch;   // 疑问1
	ch = a;       // 疑问2
	return 0;
}

操作符详解_第24张图片

这里面我们有两个疑问,我们之前说了不同类型的变量的空间大小是不一样的,我们的int是4字节,char类型是1个字节,请问他们是如何可以相互赋值的?这里就不得谈两个话题,一个是整形提升,一个是截断问题.

数据存储

谈一个耳熟能详的知识点,在计算机中我们存储的数据是补码,这一点我暂时认为大家非常了解,当然如果不太熟悉,可以去看后面的博客.

整型提升

由于CPU以四个字节计算速度快,所以在小于四个字节的的整形数据进行计算时,计算机隐形的将数据提升为四个字节,也就是补足32个比特位,下面是补全的规则.

  • 无符号前面补 0
  • 有符号前面补符号位

下面我分别举一些例子和大家分享.

//输出什么
#include 
int main()
{
    char a = -1;
    signed char b = -1;
    unsigned char c = -1;
    printf("a=%d,b=%d,c=%d",a,b,c);
    return 0;
}

操作符详解_第25张图片操作符详解_第26张图片

截断问题

所谓的数据截断就是我们把大空间的赋值给小空间的,编译器会自动的截断,把相应空间的数据给赋值过去,看代码.

#include 
int main()
{
    char a = -128;
    printf("%u\n",a);
    return 0;
}

操作符详解_第27张图片

操作符详解_第28张图片

你可能感兴趣的:(c++,c语言)