C高阶1(二进制操作)

1. 位运算

  1. 按位运算
    No. 操作符 功能
    1 & 按位与
    2 | 按位或
    3 ~ 按位取反
    4 ^ 按位异或
  2. 运算规则
    p q p & q p | q p ^ q
    0 0 0 0 0
    0 1 0 1 1
    1 1 1 1 0
    1 0 0 1 1
  3. 示例
    1. 按位与
      让某一位或某些位为0
      int n = 0xFFFF; n = n & 0x0010;

      【C高阶1(二进制操作)】截取二进制数中的一段值。
      int n = 0xFFab; n = n & 0x00FF;

    2. 按位或
      让某一位或某些位为1
      int n = 0x0000; n = n | 0x0010;

      拼接两个二进制数。
      int a = 0xab00; int b = 0x0012; int c = a|b;

    3. 按位取法
      得到全部为1的数字~0
      int n = ~0; // 等同于0xFFFF

      使数字的部分清零x& ~7
      int n = 0xFFFF; n = n & ~7;

    4. 按位异或
      两个相等数异或结果为0
      int n = 0x1234; n = n^n;

      对同一个变量两次异或,变会原值。
      int a = 0x1234; int b = 0x1357; a = a^b; a = a^b;

逻辑运算与按位运算
  1. 逻辑运算结果只有01两种值,按位运算有多种值。
  2. 逻辑运算相当于把所有的非零值都变成1,再按位运算。
  1. 练习
    • 136. 只出现一次的数字
    • 389. 找不同
2. 移位运算
No. 操作符 功能
1 << 左移
2 >> 右移
  1. 左移
    i<表示i中所有位向左移动j个位置,右边填入0
    所有小于int的类型,移位以int大小执行,结果为int
  2. 右移
    i>>j表示i中所有位向右移动j个位置,
    对于unsigned类型,左边填入0; 对于signed类型,左边填入符号位。
    所有小于int的类型,移位以int大小执行,结果为int
  3. 位移运算与乘除运算
    位移运算与乘除运算运算有如下对应关系:
    No. 位移运算 乘除运算
    1 x<<1 x*2
    2 x>>1 x/2
    3 x< x*pow(2,n)
    4 x>>n x/pow(2,n)
    5 x<<=1 x*=2
    6 x>>=1 x/=2
    7 x<<=n x*=pow(2,n)
    8 x>>=n x/=pow(2,n)
  • 试一试
    x<<-2; // 未定义行为

  • 练习
    1. 位的设置
      i |= 1<

    2. 位的清除
      i &= ~(1<

    3. 位的测试
      i & 1<

  • 实践
    用位表示下面的结构体。
typedef struct{ bool student; // true:表示学生,false:表示非学生 bool sex; // true:表示男,false:表示女 bool pass; // true:表示合格,false:表示不合格 };

3. 位域 3.1 位域是什么?
位域是又称作位段,是把一个字节中的二进位划分为几个不同的区域。
3.2 位域有什么用?
节省空间,有些信息不需要占用一个完整的字节。
3.3 位域怎么用?
  1. 定义位域
    定义位域与结构定义相仿。
    • 语法
      struct 位域结构名{ 类型 位域名:位域长度; };

      为了保证位域的可以移植性,成员类型通常为unsigned intint,C99可以使用bool
    • 示例
      struct Byte{ unsigned int b1:1; unsigned int b2:1; unsigned int b3:1; unsigned int b4:1; unsigned int b5:1; unsigned int b6:1; unsigned int b7:1; unsigned int b8:1; };

  2. 位域变量
    定义和使用位域变量与结构体相同。每个域有一个域名,允许在程序中按域名进行操作。
    void printByte(struct Byte a){ printf("%d",a.b1); printf("%d",a.b2); printf("%d",a.b3); printf("%d",a.b4); printf("%d",a.b5); printf("%d",a.b6); printf("%d",a.b7); printf("%d\n",a.b8); } int main () { struct Byte a; printByte(a); struct Byte b = {1,0,1,0,1,0,1,0,}; printByte(b); return 0; }

    试一试
    struct Byte c = {1,2,3,4,5,6,7,8,}; printByte(c);

    void scanfByte(struct Byte* a){ scanf("%d",&a.b1); scanf("%d",&a.b2); scanf("%d",&a.b3); scanf("%d",&a.b4); scanf("%d",&a.b5); scanf("%d",&a.b6); scanf("%d",&a.b7); scanf("%d",&a.b8); }

  3. 位域大小
    printf("sizeof(Byte) = %ld\n",sizeof(Byte));

    struct Byte{ unsigned char b1:1; unsigned char b2:1; unsigned char b3:1; unsigned char b4:1; unsigned char b5:1; unsigned char b6:1; unsigned char b7:1; unsigned char b8:1; };

    位域的存储由编译器实现决定。编译器规定存储单元大小(8位、16位、32位)。 编译器会把位域逐个放入存储单元,位域之间没有间隙,直到存储单元放不下下一个位域,然后从下个存储单元继续存放。也有编译器跨存储单元存放。存放顺序也是编译器决定。
    整个结构体的总大小为最宽基本类型成员大小的整数倍。

    推荐阅读