C语言学习--操作符

Posted 庸人冲

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言学习--操作符相关的知识,希望对你有一定的参考价值。

算数操作符

算数操作符包括,四则操作符 + - * / 和 取模操作符 %

+ - * 的运算规则与数学中相同,重点介绍 /%的注意点。

  1. /号两边得操作数如果都是整数,执行的是整数除法,结果为整数商,余数被舍弃。
int main()
{
    // 两边为整数
    int a = 5 / 2;     // == 2
    // 有一个浮点数
    float f = 5 / 2.0; // == 2.500000  结果默认小数点后六位     
    return 0;
}
  1. % 号两边的操作数必须都是整数,结果为整除后的余数
int main()
{
    // 两边为整数
    int a = 5 % 2;     // == 2
    // 有一个浮点数
    int b = 5 % 2.0;   // 非法,左右操作数必须为整型
    float f = 5 % 2.0; // 非法
    return 0;
}
  1. 除了 % 其他的算数操作符都可以作用于整数和浮点数。

移位操作符

移位操作符包括,右移操作符>>和左移操作符<<

移位操作符移动的是数二进制位,移位操作符只能作用与整数

二进制数

我们通常是使用10进制来表示一个数,例如1234,千位1,百位2,十位3,个位4,个位的权重为100,十位为101,百位为102,千位为103

用每一位上的数 × 权重,再相加就能得到这个10进制数。
1234 = 1 × 1 0 3 + 2 × 1 0 2 + 3 × 1 0 1 + 4 × 1 0 0 1234 = 1 \\times 10^3 + 2 \\times 10^2 + 3 \\times 10^1 + 4 \\times 10^0 1234=1×103+2×102+3×101+4×100
而计算中在存储数据是以二进制数来存储,二进制数只有1,0二种表示方式,逢2进1。例如二进制数0111,它的第0位的权重是20,第1位的权重是21,第二位的权重是22

用每一位的数 × 权重,再相加就能得到这个2进制数的十进制表示。
7 = 1 × 2 2 + 1 × 2 1 + 1 × 2 0 7 = 1 \\times 2^2 + 1 \\times 2^1 + 1 \\times 2^0 7=1×22+1×21+1×20

原码、反码、补码

计算机中用二进制数的最高位来表示正负,也称为符号位,0表示正数,1表示负数,剩余的位用来表示二进制数的绝对值。

有符号位数的二进制表示有:原码、反码、补码。

原码

最高位表示数的符号,其他位表示数值

7 == 00000111

-7 == 10000111

反码

  1. 正数的反码和原码相同

    7 == 00000111 == 7

  2. 负数的反码等于原码符号位不变,其他位按位取反。

    -7 == 10000111 <==> 11111000 == -7

补码

  1. 正数的补码、反码、原码相同

    7 == 7 == 7 == 00000111

  2. 负数的补码等于反码 + 1

    -7 == 10000111 <=> 11111000 = -7 <=> 11111001 = -7

计算机在存储数据时中存储的是二进制的补码,使用补码的方式存储是因为可以用加法同时处理加法与减法运算。

例如: 1 - 1 = 1 + (-1) = 0

  1. 用原码方式相加

    1 = 00000001,-1 = 10000001

    00000001 + 10000001 = 10000002 = -2 (错误)

  2. 用反码方式相加

    1 = 00000001,-1 = 11111110

    00000001 + 11111110 = 11111111 = 10000000 = -0

    -0 虽然和 0 相同,但是用2个组合来表示一个数有些浪费,因此不可取。

  3. 用补码方式相加

    1 = 00000001,-1 = 11111111

    00000001 + 11111111 = 100000000 = 0 (正确)

    经过计算,1会进到第8位,多出的最高位溢出被舍弃,0则变为符号位,因此得出的结果为正数,他的原反补相同。

右移操作符 >>

因为计算机中存储的是补码,所以在移位操作时,操作的也是补码的位。

右移操作又分为:

  1. 算数右移

    右边舍弃,左边补原符号位

  2. 逻辑右移

    右边丢弃,左边补0

    VS中为算数右移

// 正数原反补相同
int main()
{
	int a = 16;
	int b = a >> 1; // 右移操作符,将16的二进制位向右移动一位
	//16 == 00000000000000000000000000010000 >> 1
	// b == 00000000000000000000000000001000 == 8
	int c = a >> 2;
	//16 == 00000000000000000000000000010000 >> 2
	// c == 00000000000000000000000000000100 == 4
	printf("%d\\n", b);  // 8
	printf("%d\\n", c);  // 4
    // 右移一位相当于 十进制数 / 2;
}
// 负数
int main()
{
    int a = -16;
    int b = a >> 1; 
    // -16原码 == 1000 0000 0000 0000 0000 0000 0001 0000 
    // -16反码 == 1111 1111 1111 1111 1111 1111 1110 1111 
    // -16补码 == 1111 1111 1111 1111 1111 1111 1111 0000 >> 1
    // b的补码 == 1111 1111 1111 1111 1111 1111 1111 1000 
    // b的反码 == 1111 1111 1111 1111 1111 1111 1111 0111 
    // b的原码 == 1000 0000 0000 0000 0000 0000 0000 1000 == -8
     printf("b = %d\\n", b);
    
    int c = a >> 5;
    // -16补码 == 1111 1111 1111 1111 1111 1111 1111 0000 >> 5
    // c的补码 == 1111 1111 1111 1111 1111 1111 1111 1111 
    // c的反码 == 1111 1111 1111 1111 1111 1111 1111 1110
    // c的原码 == 1000 0000 0000 0000 0000 0000 0000 0001 == -1
    printf("%d\\n",c);
    return 0;
    
    // c的结果可以看出,负数右移时最大为-1,因为每次补的都是1,最终补码的值是全1,而全1转换为原码就是-1
}

在这里插入图片描述

左移操作符 <<

左移操作的规则只有:左边丢弃,右边补0

// 正数
int main()
{
	int a = 16;
	int b = 16 << 1;
    // a == 0000 0000 0000 0000 0000 0000 0001 0000 << 1
    // b == 0000 0000 0000 0000 0000 0000 0010 0000 == 32
    printf("b = %d\\n", b);
    
    // 左移操作符在移动时可能会改变符号
    short c = 16385; // 0100 0000 0000 0001
    short d = c << 1;
    // c的补码 == 0100 0000 0000 0001 << 1
    // d的补码 == 1000 0000 0000 0010 得到负数
    // d的反码 == 1000 0000 0000 0001 
    // d的原码 == 1111 1111 1111 1110 == -32766
      printf("d = %d\\n",d)
          
    short e = -1; 
    short f = e << 1;
    // e的原码 == 1000 0000 0000 0001 
    // e的反码 == 1111 1111 1111 1110
    // e的补码 == 1111 1111 1111 1111 << 1
    // f的补码 == 1111 1111 1111 1110
    // f的反码 == 1111 1111 1111 1101
    // f的原码 == 1000 0000 0000 0010  == -2
    	

	return 0;
}

在这里插入图片描述

注意

移位操作符,不要移动负数位,这个标准是未定义的。

int a = 10;
int b = a >> -1; // 未定义,在VS2019中结果为0

位操作符

位操作符:按位与& 按位或| 按位异或^

位操作符,同样是操作补码得二进制位,并且只能操作整数

按位与&

同二进制位都为1时得1,其余情况得0

二进制数1二进制数2按位与&结果
111
100
010
000

符号位都为1时,得负数,其余情况得正数

 // 同时为正数,原返补相同
int main()
{
   
    short a = 123;
    short b = 101;
    short res = a & b;
    // a   == 0000 0000 0111 1011
    // b   == 0000 0000 0110 0101
    // res == 0000 0000 0110 0001 == 97
    printf("%d\\n", res);
    return 0;
}

在这里插入图片描述

// 同时为负数,原反补不同
int main()
{
    short a = -123;
    short b = -101;
    short res = a & b;
    // a原码  == 1000 0000 0111 1011
    // a反码  == 1111 1111 1000 0100
    // a补码  == 1111 1111 1000 0101
    // b原码  == 1000 0000 0110 0101
    // b反码  == 1111 1111 1001 1010
    // b补码  == 1111 1111 1001 1011
    // a & b
    // a补码  == 1111 1111 1000 0101 
    // b补码  == 1111 1111 1001 1011
    // res补  == 1111 1111 1000 0001
    // res反  == 1111 1111 1000 0000 
    // res原  == 1000 0000 0111 1111== -127
     printf("%d\\n", res);
    return 0;
}

在这里插入图片描述

// 一负一正,符号位会变为0,也就是一正一负按位与&得正数
int main()
{
    short a = 123;
    short b = -101;
    short res = a & b;
    // a     == 0000 0000 0111 1011 
    // b补   == 1111 1111 1001 1011
    // res   == 0000 0000 0001 1011  == 27
    // 正数原反补相同
    printf("%d\\n", res);
	return 0;
}

在这里插入图片描述

按位或|

同二进制位都为0时得0,其余情况得1

二进制数二进制数按位或|结果
111
101
011
000
// 符号位都为0时,按位或`|`得正数,其余情况得负数
int main()
{
    // 符号位为0
    short a = 123;
    short b = 101;
    short res = a | b;
    printf("%d\\n", res);   // 127

    // 符号位为1
    short a1 = -123;
    short b1 = -101;
    short res1 = a1 | b1;
    printf("%d\\n", res1); // -97

    // 符号位不同
    short a2 = 123;
    short b2 = -101;
    short res2 = a2 | b2;
    printf("%d\\n", res2); // -5
    return 0;
}

在这里插入图片描述

按位异或^

同二进制为不同时为1,相同时为0

二进制数二进制数按位异或^结果
110
101
011
000
// 符号位相同时,按位异或^ 得正数,符号位不同时得负数。
int main()
{
    // 符号位为0
    short a = 123;
    short b = 101;
    short res = a ^ b;
    printf("%d\\n", res);   // 30

    // 符号位为1
    short a1 = -123;
    short b1 = -101;
    short res1 = a1 ^ b1;
    printf("%d\\n", res1); // 30

    // 符号位不同
    short a2 = 123;
    short b2 = -101;
    short res2 = a2 ^ b2;
    printf("%d\\n", res2); // -32
    return 0;
}

在这里插入图片描述

练习

练习1

不创建变量,交换两个数的值

int main()
{
	int a = 10;
	int b = 20;
	a = a ^ b;  // 30  a^b                 得到一个结果
	b = a ^ b;  // 10  a(结果)^b            得到原a的数值
	a = a ^ b;  // 20  a(结果)^b(原a的值)   得到原b的值
	printf("a = %d , b = %d \\n",a, b);
   
    // 加减实现,原理相同,缺点当两个值非常大时,+运算可能会溢出,导致二进制位丢失
    a = a + b;   // 20 + 10 = 30
    b = a - b;   // 30 - 20 = 10 
    a = a - b;   // 30 - 10 = 20
    // 结果又交换回来
    printf("a = %d , b = %d \\n",a, b);
    
    
	return 0;
}
// 0000 1010 // a
// 0001 0100 // b
// 0001 1110 // a = 16+8+4+2 = 30  
// 0001 0100 // 
// 0000 1010 // b = 10
// 

在这里插入图片描述

练习2

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

注意:计算机在计算时,计算的是二进制补码中1的个数。

方法1:(不能计算负数)

  1. 创建一个变量num接收一个整数。

  2. 算出这个整数的每一位二进制数。

    十进制转二进制,是十进制不断除以2,直到商为0,去商为0的余数往前拼接每一位余数得到的就是2进制数。

    1. 先让num % 2,得到当前数的余数。

    2. 再让 num = num / 2,获得下次做运算的数。

    3. 一直循环直到num = 0

  3. 创建变量用于统计1的个数。

  4. 在循环过程中,只要num % 2 == 1,count++,最后count的值就是这个二进制数中1的个数。

// 这种算法不能计算负数!
int main()
{
	int num = 0;
    scanf("%d", &num);
	int count = 0;
	while(num)
	{
		if (num % 2 == 1)
		{
			count++;
	   }
	   num = num / 2;  
	}
	printf("%d\\n", count);
	return 0;
}

方法2:

每次判断数的二进制最低位是否为1,判断方法如下:

  1. num & 1 ,如果num的最低位为1则结果为十进制数1,count++;
  2. 让num右移一位,继续判断,直到这个类型的数中每一位都被判断。
int main()
{
    int num = 0;
    int count = 0;
    scanf("%d",&num);
    for(int i = 0; i < 32;i++) // int占32位空间
    {
		if( 1 == ((num>>i) & 1))
        {
			count++;
        }
    }
    printf("%d\\n",count);
	return 0;
}
// 正数 原反补相同
// 例如 num = 15
// 0000 0000 0000 0000 0000 0000 0000 1111  
// 0000 0000 0000 0000 0000 0000 0000 0001  & 1
// 0000 0000 0000 0000 0000 0000 0000 0001  结果为1
// count++;

// 右移一位
// 0000 0000 0000 0000 0000 0000 0000 0111
// 0000 0000 0000 0000 0000 0000 0000 0001  & 1
// 0000 0000 0000 0000 0000 0000 0000 0001  结果为1
// count++;

// 右移一位
// 0000 0000 0000 0000 0000 0000 0000 0011
// 0000 0000 0000 0000 0000 0000 0000 0001  & 1
// 0000 0000 0000 0000 0000 0000 0000 0001  结果为1
// count++;

// ... 直到最后一位判断完毕




// 负数 原反补不同
// 例如num = -15

// 1000 0000 0000 0000 0000 0000 0000 1111
// 1111 1111 1111 1111 1111 1111 1111 0000 反码
// 1111 1111 1111 1111 1111 1111 1111 0001 补码,29个1
// 0000 0000 0000 0000 0000 0000 0000 0001  & 1
// 1000 0000 0000 0000 0000 0000 0000 0001  结果
// count++;    count == 1

// 右移一位
// 1111 1111 1111 1111 1111 1111 1111 1000
// 0000 0000 0000 0000 0000 0000 0000 0001  & 1
// 0000 0000 0000 0000 0000 0000 0000 0000  结果

// ... 

//右移到第4位,此时二进制位全为1
// 1111 1111 1111 1111 1111 1111 1111 1111 
// 0000 0000 0000 0000 0000 0000 0000 0001 & 1
// 0000 0000 0000 0000 0000 0000 0000 0000  结果
// count++;  count == 2

// 再右移一位
// 1111 1111 1111 1111 1111 1111 1111 1111
// 0000 0000 0000 0000 0000 0000 0000 0001 & 1
// 0000 0000 0000 0000 0000 0000 0000 0000  结果
// count++;  count == 3

// ... 直到右移到最高位

方法3:

#include <stdio.h>
#include <stdlib.h>
int main()
{
	int num = -1;
	int i = 0;
	int count = 0;

	while (num)
	{
		count++;
		num = num&(num-1);
	}

	printf ("二进制中1的个数 = %d\\n",count);
	system ("pause");
	return 0;
}
// num = -1
// num原码:1000 0001    
// num反码:1111 1110    
// num补码:1111 1111    

// 第一次循环
// 1. count++;    count == 1
// 2. 执行num - 1
//    num补码:	 1111 1111    
//    1的补码:		0000 0001        
//    num - 1结果:  1111 1110  // -2  
// 3. 执行num & (num - 1)
//    num补码	 	  1111 1111 
// 	  num - 1       1111 1110 
//    结果,补码   	1111 1110    
//    结果,反码     1111 1101    
//    结果,原码     1000 0010    
//    num == -2,进入第二次循环

// 第二次循环
// 1. count++;    count == 2
// 2. 执行num - 1
//    num补码:	 1111 1110
//    1的补码:		0000 0001 
//    num - 1结果:  1111 1101  // -3
// 3. 执行num & (num - 1)
//    num补码	 	  1111 1110 
// 	  num - 1       1111 1101
//    结果,补码    	1111 1100 
//    结果,反码     1111 1011
//    结果,原码     1000 0100
//    num == -4,进入第三次循环


// 第三次循环
// 1. count++;    count == 3
// 2. 执行num - 1
//    num补码:	 1111 1100
//    1的补码:		0000 0001 
//    num - 1结果:  1111 1011   // -5
// 3. 执行num & (num - 1)
//    num补码	 	  1111 1100
// 	  num - 1       1111 1011
//    结果,补码    	1111 1000 
//    结果,反码     1111 0111
//    结果,原码     1000 1000 
//    num == -8,进入第四次循环

赋值操作符

= 右边的值赋给左边,左值必须是可操作数,不能是常量 。

连续赋值

这种写法不推荐,语义表达不清楚

a = x = y + 1;
// ⬅⬅⬅⬅⬅⬅⬅⬅⬅
// 执行顺序从右往左
// 先将y + 1赋值给 x;
// 再将 x 赋值给 y

符合赋值操作符

以上是关于C语言学习--操作符的主要内容,如果未能解决你的问题,请参考以下文章

我的C语言学习进阶之旅解决 Visual Studio 2019 报错:错误 C4996 ‘fscanf‘: This function or variable may be unsafe.(代码片段

我的C语言学习进阶之旅解决 Visual Studio 2019 报错:错误 C4996 ‘fscanf‘: This function or variable may be unsafe.(代码片段

如何有条件地将 C 代码片段编译到我的 Perl 模块?

Qt编程遇到的问题,我在qt中直接使用C语言的程序片段,有问题 ,求解

学习笔记:python3,代码片段(2017)

需要示例代码片段帮助

(c)2006-2024 SYSTEM All Rights Reserved IT常识

操作符用法等价
+=a += 1a = a + 1
-=a -= 1a = a - 1
*=a *= 1a = a * 1
/=a /= 1a = a / 1
%=a %= 2a = a % 2
>>=a >>= 1a = a >> 1
<<=