操作符超详解(下)

1、移位操作符:<<、>>

2、位操作符:&、|、~、^

3、单目操作符:*、~、&

4、下标引用操作符:[ ]

5、函数调用操作符:()

6、结构体访问操作符:   .    ->

一、简单概述

        在介绍下篇操作符之前我先介绍一下,其中,移位操作符、位操作符的使用是在整形变量里,而整形数据存放内存中其实存放的是补码,所以开篇我会介绍一下二进制里的原码、反码、补码的基础概念,解引用操作符(*)和,取地址操作符(&),涉及指针的运用,下标引用操作符涉及数组。

二、原码,反码,补码

2.1进制转换

  怎么获取,原码,将十进制数转化位二进制数所获得的即是原码,最高位为符号位,主要转换方式:

除2取余法:将一位十进制数除2,记录余数,再将商除2,记录余数,如此循环反复,直到到商为0时停止,然后对余数逆序排序,最后记录的余数做二进制个位......第一次记录的余数做最后一位

  如下,将一个十进制13转换为二进制数

  13 / 2 = 6 ---1

  6 / 2 = 3 ---0

  3 / 2 = 1 --- 1

  1 / 2 = 0 --- 1

        按照上述,最后转换所得的二进制数为:1101

  知道了如何将十进制转换为二进制,那反过来说怎么将二进制数转换为十进制数?

权重累加法:从二级制的最低位开始,每一位乘上它的权重(2的幂次,从0开始,每向前进一位,次数加一位),然后累加它们的结果。

  如下是,将一个二进制数1101转换为十进制

1101从最低位开始,

  1 * 2 ^ 0 = 1

  0 * 2 ^ 1 = 0

  1 * 2 ^ 2 = 4

  1 * 2 ^ 3 = 8

  最终转换后的十进制数位:8+4+0+1 = 13

2.2原反补三码

       有了如何对进制转换的基础理解后,接下来开始是正式介绍原反补三码

      整数的二进制表现方式有三种,分别是原码,反码,补码。整数在三码里分为两部分:符号位数值位。

      符号位的的表达方式:0表示正数,1表示为负数

      整数的原码,反码,补码均相同。 如一个整数10的二进制表现

        原码:0000 1010

        反码:0000 1010

        补码:0000 1010

      负数的原码,反码,补码均不相同。如一个负数10的二进制表现

        原码:1000 1010

        反码:1111 0101

        补码:1111 0110

  原码:直接将数值按照正负数的形式转换位二进制所得就是原码。

  反码:再原码的基础上按照数值位按位取反。

  补码:反码+1(二进制)得到的就是补码

三、移位操作符

  移位操作符:<<、>>,顾名思义它们是在位置上进行移动,移动的是数值存放在内存里的二进制数,我们知道一个整形在空间里所占4的字节的大小,而八个比特位又等于一个字节,所以一共有32个比特位,一个比特位用来存放一个二进制数,例如,整形10在内存里的存储形式:

  再强调一次,最高位是它的符号位,整数在的二进制里的原码、反码,补码均相同,所以上述位10进制的补码。

  3.1 左移操作符

  <<  左移操作符,将整形10的补码向左移动一位

int a = 10;
int b = a << 1;

  这里就代表着a的补码向左移动移动了一位,若操作符右边的整数是2,3,那补码就向左移动2位,三位。

 0000 0000 0000 0000 0000 0000 0000 1010   移动前
0000 0000 0000 0000 0000 0000 0000 1010    移动后

  将左移二进制后的最低位开始补0,移动了几位补几个0,多余部分将其删除

 0000 0000 0000 0000 0000 0000 0000 1010   移动前
0000 0000 0000 0000 0000 0000 0000 1010    移动后

补0
 0000 0000 0000 0000 0000 0000 0000 1010   移动前
0000 0000 0000 0000 0000 0000 0000 10100   移动后

多余的0删去

 0000 0000 0000 0000 0000 0000 0000 1010   移动前
 000 0000 0000 0000 0000 0000 0000 10100   移动后

调整
0000 0000 0000 0000 0000 0000 0001 0100     最终结果
int main()
{
	int a = 10;
	int b = a << 1;
	int c = a << 2;
	printf("%d %d\n", b, c);
	return 0;
}

  在这里,将整形10左移一位后,和左移两位后的结果

  有没有发现,我们将一个整形左移后它的大小变为了原来的两倍,接着这个思考,我们可以使用不同的整形来左移不同的位数,来发现规律。注意在左移操作符不能移动负数位,这是标准未定义的。

3.2 右移操作符

  右移操作符(>>)与左移操作符有些不同,基于左移操作符想一想,在左移操作符里我们是没有改变补码的符号位,一但我们使用右移操作符,会对整形的符号位改变。

0000 0000 0000 0000 0000 0000 0000 1010   移动前

 0000 0000 0000 0000 0000 0000 0000 1010  移动后

  右移操作符在移动后是补0,还是补别的数。

  右移运算分两种:

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

 2.算数右移:左边用该补码的符号位填充,右边抛弃

  在Visual Studio 22,19版本里进行的是逻辑右移,不同的编译器移动规则一样。

0000 0000 0000 0000 0000 0000 0000 1010   移动前

00000 0000 0000 0000 0000 0000 0000 101   移动后

0000 0000 0000 0000 0000 0000 0000 0101   调整
int main()
{
	int a = 10 >> 1;
	int b = -10 >> 1;
	printf("%d %d\n", a, b);
	return 0;
}

  将10,-10右移两位

  注意:使用右移操作符时不能移动负数位。 

四、位操作符

  位操作符:&、|、~、^

  位操作符也是在整形的二进制补码上进行操作,在正数上使用 

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

4.1 按位与&

  这里我还是以10举例子:

  -10

  1000 0000 0000 0000 0000 0000 0000 1010  原码
  1111 1111 1111 1111 1111 1111 1111 0101        反码
  1111 1111 1111 1111 1111 1111 1111 0110        补码

 10

  0000 0000 0000 0000 0000 0000 0000 1010

 10 & -10

  0000 0000 0000 0000 0000 0000 0000 1010
  1111 1111 1111 1111 1111 1111 1111 0110

  相异取0,相同不变,满足交换律

  10按位与-10后的结果:

  0000 0000 0000 0000 0000 0000 0000 0010


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

    将最后的补码装换为十进制后结果打印为2。

  4.2按位或 |

  10 | -10

  0000 0000 0000 0000 0000 0000 0000 1010  10的补码
  1111 1111 1111 1111 1111 1111 1111 0110  -10的补码

  全为0取0,其余取1,满足交换律

  10 按位或 -10后的结果:

  1111 1111 1111 1111 1111 1111 1111 1110

  将其转换为原码后:

  1000 0000 0000 0000 0000 0000 0000 0010

  它的十进制数位-2,不信可以取运行一下代码。

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


4.3 按位异或 ^

  10 ^ -10 

  0000 0000 0000 0000 0000 0000 0000 1010  10的补码
  1111 1111 1111 1111 1111 1111 1111 0110  -10的补码

  相异取1,相同为0,满足交换律

  10 按位异或 -10的结果为

  1111 1111 1111 1111 1111 1111 1111 1100

  转换为原码后

  1000 0000 0000 0000 0000 0000 0000 0100

  将其转换为十进制数为:-4

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

4.4 按位取反 ~

 按位取反不同于上述三个双目操作符,它是一个单目操作符,只有一个操作数,将一个整数的二进制补码全部取反。

  0000 0000 0000 0000 0000 0000 0000 1010  10的补码

 将其取反后

  1111 1111 1111 1111 1111 1111 1111 0101       ~10

  原码:

  1000 0000 0000 0000 0000 0000 0000 1011

  它的十进制值为:-11

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

五、移位,位操作符的应用

  例一:不创建临时变量交换两个数

  假定a = 5,b = 3我们创建第三个变量来交换a和b的值,我们都知道,之前交换两个变量的值时,是创建了第三个变量来实现的,但现在是不能使用第三个变量,不说废话,这里我们可以运用上按位异或操作符。

int main()
{
	int a = 5;
	int b = 3;
	int temp = a;
	a = b;
	b = temp;
	printf("%d %d", a, b);
	return 0;
}

  按位异或结论1:两个相同的整形按位异或后值为0。

  5的补码:0000 0000 0000 0000 0000 0000 0000 0101

   5 ^ 5:

                  0000 0000 0000 0000 0000 0000 0000 0000

  明显5 按位异或5后的为0

  那 a ^ b ^ a 的值就是为b,若有  a = a ^ b;  b = a ^ b;b的结果不就是a的值吗?结合写在一起就是:b = a ^ b ^ b; 这样就完成了一次交换。最后在交换一次 : a = a ^ b; 就完成最后一次交换

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

  例二:求一个整数存储在内存里的二进制1的个数

    首先在对整数进行补码上的操作时,整数1是是很常见的运用,就比如:

  5 & (1 << 0)

  0000 0000 0000 0000 0000 0000 0000 0110

  0000 0000 0000 0000 0000 0000 0000 0001

  5和1按位与后,结果为0

  5 & (1 << 1)

  0000 0000 0000 0000 0000 0000 0000 0110

  0000 0000 0000 0000 0000 0000 0000 0010

  5和1左移2后按位异或结果为4,在循环一次5 & (1 << 2)

  最后值为2,这是整数5的二进制1的个数

  5 &(5 - 1)

  0000 0000 0000 0000 0000 0000 0000 0110

  0000 0000 0000 0000 0000 0000 0000 0100

  结果为4,在进行一次4 &(4 - 1)

  0000 0000 0000 0000 0000 0000 0000 0110

  0000 0000 0000 0000 0000 0000 0000 0001

  结果为 0,这里就统计了整数5的二进制1的个数

  总结:  num = num & (num - 1);

               num & (1 - i);i++;

  我们根据这两个总结就可以计算其二进制1的个数

实现代码1:
int main()
{
	int num = 6;
	int count = 0;
	for (int i = 0; i < 32; i++)
	{
		if (num & (1 << i))
		{
			count++;
		}
	}
	printf("%d\n", count);
	return 0;
}

  二进制位,置0或置1,假如有一个整数5我们需要将它的第四位置为1,然后又置为0

  这里同样巧妙运用了整数1。

  0000 0000 0000 0000 0000 0000 0000 0110

  0000 0000 0000 0000 0000 0000 0000 0001

  0000 0000 0000 0000 0000 0000 0000 1110

  不难发现 5 | 1 后 将其个位置1,那我们将1向左移3,在按位或,就可以使5的第四位置1,接下来怎么置为0?我们将5 & 1,就全为0了我们不能全为0,但又要使用按位或才能将5的第四位二进制数置为0,那我们将1全部取反就不会影响到其它数字了,然后将1左移3。

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

六、指针的操作符

  *(解引用操作符)、&(取地址操作符),是运用在指针里的操作符。

  什么是指针?指针是一种变量,它用于存放数据的地址,指针可以使程序直接操作内存,指针也可以这样理解,指针就是地址,地址就是指针。

int a = 10;
int* p = &a;

    上述代码里的,int* p 就是一个指针变量,p是指针变量名,单独去除变量名就是指针类型,它的类型是int* ,int 说明它的指向对象是整形,*号用于说明变量p是一个指针,这里&a就是将整形a的地址取出来放在了指针变量的,通过操控它的地址,我可以更灵活的使用变量a。

  那我们如何取使用它?就是通过解引用操作符操控变量a,一下就是一份简单的对指针如何使用

  这里指针变量p存放着a的地址,使用%p,占位符打印

  对指针变量p进行解引用就可以同过地址来找到a的值,将其赋予2后改变了原始值。

  到这里有没有发现我们经常使用的scanf函数,使用了&操作符

int main()
{
	int a = 10;
	int* p = &a;
	*p = 2;
	printf("%p  %d\n", p, a);
	return 0;
}

  以上是对关于指针和其操作符的基础理解,当然指针的运用并不局限于这么一点点,它的操做空间相当的大,我将在后续文章详细介绍。

七、下标引用,函数调用操作符

7.1下标操作符

  []( 下标引用操作符),应用于我们使用的数组里。很容易理解和上手的。

  都知道,数组的下标从0开始,arr[2],调用了数组的第三个元素。

int arr[3] = {1, 2, 3};
int a = arr[2];

   但我们还可以使用解引用操作符来调用数组元素,在一维数组里,数组名表示的其实是一个地址,表示它首元素的地址,数组arr 是等同于 &arr[0] 的。

int main()
{
	int arr[5] = { 2,3,4,5,6 };
	int* p = arr;
	*p = 7;
	printf("%d\n", *arr);
	printf("%d\n", *(arr + 3));
	return 0;
}

7.2函数调用操作符 

  函数调用,我们第一次使用C语言就用到的操作符,在printf函数就是使用了它,函数调用操作符主要的点是,在调用函数时它最少只有一个操作数,就是函数名本身。

这里我创立了一个简单的不需要返回值的函数,应证了上述观点。

void print()
{
	printf("加油哦!!!\n");
}
int main()
{
	print();
	return 0;
}

八、结构体访问操作符

        .   ->

  结构是⼀些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量,如: 标量、数组、指针,甚⾄是其他结构体。

  当我们需要对一个学生进行描述时,我们需要名字、年龄、学号、⾝⾼、体重等,这里我们就可以使用结构体变量

  这里对于结构体并就不展开介绍了。

  第一个操作符 (  .  ),用于对结构体成员的直接访问,是双目操作符

  使用方法:结构体变量名.成员名


struct point//声明结构体变量
{
	int x;
	int y;
};

int main()
{
	struct point p1 = { 10, 20 };//初始化
	printf("%d %d\n", p1.x, p1.y);
	struct point p = { .x = 10, .y = 5 };//指定顺序初始化
	printf("%d %d", p.x, p.y); //访问结构体内的变量
	return 0;
}

  ->  用于间接访问结构成员

struct stu
{
	char name[20];//姓名
	int age;//年龄
	int num;//学号
};
int main()
{
	struct stu s = { "wangwu", 19, 202405027};
	struct stu* p = &s;
	printf("%s %d %d\n", p -> name, p -> age, p -> num);
	return 0;
}

  当然还可以使用->来对成员进行赋值。 

九、操作符的优先级、结合性

  操作符的两个属性,优先级:决定了操作符运算时的先后顺序。结合性:决定了操作符在运算过程中的顺序。比较常见的又赋值操作符(=)优先级最低,后置++、--的优先级最高。

这个网站提供了所有操作符的优先级和结合性运算:https://zh.cppreference.com/w/c/language/operator_precedence

  • 69
    点赞
  • 65
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值