C语言中的位运算&结构体浅析 — <编程之美>1.2学习笔记

引子问题:

中国象棋将帅问题:

在一把象棋的残局中,象棋双方的将帅不可以相见,即不可以在中间没有其他棋子的情况下在同一列出现。而将、帅各被限制在己方的3*3的格子中运动。相信大家都非常熟悉象棋的玩法吧,这里就不详细说明游戏规则了。

用A、B代表将和帅,请写出一个程序,输出A、B所有合法的位置。要求在代码中只能用一个变量。

分析与解法:

这个问题的解法并不复杂。

遍历A的所有位置

             遍历B的所有位置

                     如果A的位置和B的位置在同一列

                               输出结果

                    否则      继续寻找

地图可以用0-8表示A或B可能的9个位置

                    0------1------2                   

                    3------4------5

                    6------7------8

关键问题在于只使用一个变量来表示A和B的位置。所以可以使用位运算来解决。一个无符号字符类型的长度是1字节,也就是8位,8位可以表示2^8=256个值,对于A、B的9个位置来说足够。可以用前4位来表示A的位置情况,后4位表示B的位置情况。而4位可以表示16个数也足够表示A、B的位置情况了。

通过位运算可以对A、B的位置进行读取和修改。

几种基本的位运算

  (1)&  按位与运算   

  (2)| 按位或运算  "与"和"或"就不用说了吧

  (3)^ 按位异或运算 相同为假,不同为真

  (4)~ 按位取反 一元运算符

  (5)<< 按位左移  如 0000 0111 << 2 = 0001 1100,将此数左移两位相当于将此数扩大两倍。

  (6)>> 按位右移 如  0001 1000 >> 2 = 0000 0110,将此数右移两位相当于将此数缩小两倍。 

令LMASK为1111 0000,另任意一个1字节的字符型变量与其做与运算,结果右移四位,便可得到此变量的高四位的值。

Example,

   0110 1011

&1111 0000

= 0110 0000 >> 4 = 0000 0110

同理,令RMASK为0000 1111,即可得到它低四位的值。

Ex.

   0110 1011

& 0000 1111

= 0000 1011

设置1字节字符型变量,比如对高四位进行设置,先将变量与RMASK相与,将要修改的变量左移四位后于前一结果进行“异或”或“或运算”。

Ex.将0110 1011高四位设置为1001.

   0110 1011

& 0000 1111

= 0000 1011              0000 1001 << 4 = 1001 0000

^ 1001 0000

= 1001 1011

同样的方法设置低四位的值。

代码:

#include<stdio.h>

#define HALF_BITS_LENGTH    4                                                                            //一半字节的长度
#define FULLMASK                 255                                                                        //即1111 1111
#define LMASK                      (FULLMASK << HALF_BITS_LENGTH)                     //即1111 0000
#define RMASK                      (FULLMASK >> HALF_BITS_LENGTH)                    //即0000 1111
#define RSET(b, n)                  (b = (LMASK & b) ^ n)                                          //设置低四位
#define LSET(b, n)                  (b = (RMASK & b) ^ (n << HALF_BITS_LENGTH)) //设置高四位
#define RGET(b)                     (RMASK & b)                                                        //得到低四位
#define LGET(b)                     ((LMASK & b) >> HALF_BITS_LENGTH)                //得到高四位
#define GRIDW                     3                                                                          //将帅活动范围尺寸
#define BYTE                         unsigned char

int main(void)
{
    BYTE b;     //只有一个变量
    for(LSET(b, 1); LGET(b) <= GRIDW * GRIDW; LSET(b, (LGET(b) + 1)))//从A的1位置一直找到9位置,注意自增的写法
        for(RSET(b, 1); RGET(b) <= GRIDW * GRIDW; RSET(b, (RGET(b) + 1)))//从B的1位置找到9位置
            if((LGET(b) % GRIDW) != (RGET(b) % GRIDW))   //如果不在同一列,则打印结果
                printf("A = %d, B = %d\n", LGET(b), RGET(b));
    return 0;
}

这是个关于如何利用位运算解决问题的一个简单的运用,可以看到位运算合理地利用一个变量解决象棋将帅问题。算法本身很简单,重点是位运算的应用。


<BOP>上还有两个更简洁的算法:

第一个:

#include<stdio.h>

#define BYTE unsigned char

int main(void)
{
    BYTE i = 81;
    while(i--)
    {
        if((i / 9) % 3 == (i % 9) % 3)
            continue;
        printf("A = %d, B = %d\n", i /9 + 1, i%9 + 1);
    }
    return 0;
}

可以把变量i想象成一个两位九进制的变量,而i在计算机中存储的值是i的十进制表示。则i/9的计算机处理结果,即结果直接去掉小数点后部分的结果即是此九进制数的第二位,而i%9即是此九进制数的个位。本程序用此九进制数的第二位保存A的位置,个位表示B的位置。最大值为88,即为十进制的80.程序从十进制的80,即九进制的88遍历到十进制的0,即九进制的0.将符合条件的位置全部输出。


第二个:

#include<stdio.h>


int main(void)
{
    struct
    {
        unsigned char a:4;
        unsigned char b:4;
    }i;


    for(i.a = 1; i.a <= 9; i.a++)
        for(i.b = 1; i.b <= 9; i.b++)
            if(i.a % 3 != i.b % 3)
                printf("A = %d, B = %d\n", i.a, i.b);
    return 0;
}

算法与上面的如出一辙。

其中unsigned char a:4表示结构体中a的位域只有4位,高位用作它用。只能在结构体里使用,建议尽量少用,会破坏程序的移植性。

当结构体中的元素的取值范围很小时,可以将几个字段按位合成一个字段来表示,起到节省内存空间的作用。

Ex:

#include<stdio.h>

int main(void)
{
    struct test
    {
        unsigned char a:4;
        unsigned char b:4;
    }i;
    i.a = 15;
    i.b = 10;
    printf("%d\n", sizeof(i));
}

将上面例子中的变量i的大小输出,结果为1字节。说明i.a和i.b各占4位。

结构体是C语言中的一种常用的自定义数据结构。

看下面的例子:

#include<stdio.h>

int main(void)
{
    struct test
    {
        int a;
        char b;
    }i;
    printf("%d\n",sizeof(i));
}

按理说结构体变量i的大小应该是sizeof(int)+sizeof(char),即5,而输出显示的结果为8。再看一个例子:

#include<stdio.h>

int main(void)
{
    struct test
    {
        int a;
        char b,c;
    }i;
    printf("%d\n",sizeof(i));
}

应该是6对吧?结果还是8.这是为什么呢?

这是因为在32位的操作系统上,操作系统组织数据是以32位(4个字节)作为一个标准,因此各种变量的size都一般都是4的倍数。而且结构体数据都是按照定义时所使用的顺序存放的,因此在第一个例子中尽管b变量只会占有一个字节,但是a + b = 5 > 4,因此第一个4个字节存放a,第二个4个字节用于存放b,这样实际上就浪费了3个字节。在第二个例子中第二个4个字节用来存放b和c。

所以,在结构体中要注意结构体中的变量定义的顺序,不同的顺序可能会造成占用空间的不同。这在嵌入式程序设计等系统资源比较少的情况下尤为重要。比如如下两种结构体:

#include<stdio.h>

struct m
{
    char a;
    int b;
    char c;
}x;
struct n
{
    char a;
    char c;
    int b;
}y;
int main(void)
{
    printf("m:%d\nn:%d\n", sizeof(x), sizeof(y));
    return 0;
}

对于结构体m来说,x变量的大小为12,而y变量的大小为8.编译器是按程序员在结构体中声明变量的顺序处理的。不当的顺序会造成空间的浪费。读者可以想到发生这样情况的原因的。所以建议声明结构体时,按照不同变量的类型,按占用空间的大小升序或降序声明会取得较好的空间占用


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值