C语言运算符应用

一、常用位运算和使用场景总结

1. 常用位运算操作行为

我们知道,物理层面的最小单位是bit,而软件层面是Byte。C语言提供了访问bit的工具——位运算符号。

  • & 与,有如下特性,所以我们可以用它来清除某一个位,即拿0去和一些位做操作,可以让它们都变成0。只要说清除就是置0。
    • 1 & A = A
    • 0 & A = 0
  • | 或,有如下特性,所以我们可以用它来设置某一个位,即拿1去和一些位做操作,可以让它们都变成1。只要说设置就是置1。
    • 1 | A = 1
    • 0 | A = A
  • >> 右移,补位与数据类型有关。
  • << 左移,末位补0。
  • ~ 取反,~0 = 1, ~1 = 0

2. 设置某区域的值

有一个32bit的内存空间,假设标记为a int a;

  1. 对这段空间的第5bit设置为1。(从0开始的第5bit)
    a | 0x1<<5,a 或 0x1左移5位,这样可以避免写很长的数去

  2. 对这段空间的第5bit清除为0。
    清除是&0,又要保持其他位不变,所以我们需要构造一个第5位为0,其他位为1的数。所以需要先构造第五位为1,其他为0的数,再取反。
    a & ~(0x1 << 5)

  3. 对这段空间的5678位设置1011.
    由于第5678位的具体的值未知,所以我们要先清除这几位的值(0x0位移没有意义!),再进行设置。
    a & (~0xf << 5) //注意先取反还是先左移
    a | (0xb << 5)

3. 异或的灵活应用

^ 同0异1

  1. 交换两个数,不引入中间变量

    int a1 = 20, a2 = 10;
    a1 = a1 ^ a2;
    a2 = a1 ^ a2;	// 此时的a2 = 原来的 a1 ^ a2 ^ a2 == a1
    a1 = a1 ^ a2;	// 此时的a1仍为原来的 a1 ^ a2, 而此时的a2已经为a1,现在的赋值就相当于什么都没变时的 a1 ^ a2 ^ a1 == a2。完成了交换
    
  2. 对称加密
    dlkfajsdf ^ key1得到密文
    密文 ^ key1得到原始信息

  3. 给你一堆数据,两两成对出现,只有1个数据是单个的,让我们从这堆数据中,找出这个单个的数据是什么?
    一起做一次异或a1^a2^a3......

4. 统计一个int空间中1的个数

有一个特性:a & a-1每做一次,a中1的个数会减少一个。
所以我们只需要不断地运行a & a-1,直到a为0,统计运算的次数,这就是a中的1的个数

二、其他运算符梳理

1. 运算符

+ - * / : 存在越界、容量够不够的问题
%: 求余数,得到的是一个范围的数,一个循环的数。循环队列会用到。

2.逻辑运算符

逻辑与位的要区分开

  • 逻辑:真和假。0(NULL等价于0)为假,其他都为真。
  • 位状态:1和0——高电平和低电平

~a!a

  • ~a是逐位取反,值可大可小
  • !a是逻辑取反,真假转换

3.自增、自减

C语言提供的运算符,都不会改变变量本身的值,都需要和等号进行复合

a++++a

  • a++是先把a暂存起来,放到一个临时位置,然后a本身的值加一。整个表达式的返回值是依靠这个临时位置的值的。
  • ++a是a自身的内容加1,改变了a里面原来的值。表达式返回现在的a。

所以显然,++a的效率更高,虽然现代编译器能优化,但是我们尽量在能用++a的地方用它。

三、不同容量内存的赋值逻辑

char a = 0xabcd;
一个char的空间只有1字节,也就是8位,而0xabcd有16位,所以是“小的容器装大的内容”,会发生截取
而如果是大的容器装小的,则会补填符号位

sizeof(0xabcd)的值不为2,因为0xabcd是数字常量,默认是int大小。

printf("%x")中,%x的意思是,把int这么大的内存的值,当作16进制显示在屏幕上。另外还有%hx,是short这么大的;%hhx是char这么大的。

四、指针const的结合,typedef

typedef 是对数据类型进行别名的

char *str_t;          // 定义了一个叫做str_t的地址类型的变量,指向了一个char空间的地址
typedef char *str_t;        // 定义了一个叫做str_t的地址类型的别名,指向了一个char空间的地址       

const str_t s1 = s;	//相当于char *const s1 = s; const是修饰这个*的,所以它指向的地址不能变,而地址中的内容可以变

const char *s2 = s;	//const修饰的是指向的内容,也就是说,s2指向的东西可以变,而指向的东西的内容不能变

SUFE大二在读