讀古今文學網 > C語言解惑 > 17.2 位運算典型實例 >

17.2 位運算典型實例

【例17.9】使用數據作標誌的例子。


#include <stdio.h>
struct {
       unsigned a
:1
;
       unsigned b
:2
;
       unsigned c
:3
;
       int d
;
} s
;
int main
( 
)
{
     s.a=1
;    s.b=2
;
     s.c=3
;    s.d=4
;
     printf 
(\"%d %d %d %dn\"
, s.a
, s.b
, s.c
, s.d
);
     return 0
;
}
  

程序輸出結果為:1234

當數據是整型而又取數不大時,為節省存儲空間,可在一個字內放幾個數據。例如,用數據作標誌時,有1位就足夠了,這叫標誌位。標誌位的值要麼是1,要麼是0。在C語言裡,不滿一個字的整型變量,也可以作結構成員。

由該例題可以看到,在結構定義中,成員後面有冒號和數字。冒號表示成員是不滿一個字的整型數據。這樣的成員就叫字段。為表示字段是無正負號的量,字段必須用類型關鍵字unsigned來定義。冒號後面的數字表示字段的長度。當然,其值要小於字長。如果字長為16位,這數字就小於16。

字段的大小不能超過字長。如果幾個字段超過了一個字的大小,那麼,編譯程序就把該字段移到下一個字。這時,會有若干位不被使用。

此外,若想強制某字段放在一個字的開始部分,只要在該字段的前面寫一個長度為0的字段即可,如:

unsigned a:1;

unsigned c:0;

unsigned b:3;

這樣,字段b便成為一個字中的開始字段了。

如果不想使用的字段,就像上面那樣,不寫字段名,代替0而寫出位數。這樣的字段叫無名字段,它可用來填空。

使用字段,必須注意如下幾點:

(1)在IBM PC及其兼容機上,不支持除無符號整數外的其他類型的字段。因此,在說明字段時,必須使用關鍵字unsigned,即使使用int都不行。

(2)字段不存在地址,不能使用運算符「&」。因而,也無指向字段的指針。

(3)字段在一個字上的分配方向,因機器而異。

(4)不能構造字段數組,必須一個字段一個字段地進行定義。

(5)字段和其他類型成員之間,可有不被使用的位。這也適用於結構成員之間。但是,什麼情況下產生間隙與機器有關,例如,若ps為指向結構的指針,而pc為指向字符的指針,則按如下方式進行指針置換,


pc = 
( char* 
) ps
;
  

就能使用指向字符型的指針pc,以字節為單位引用結構。但是,在結構中有時也有間隙。因而,若以字節為單位引用結構,有時也能引用沒有定義的存儲單元。

【例17.10】統計一個數的二進製表示哪位是1及包含1的個數。

【解答】設這個數為num,先分析一個具體數字。假設num=100,表示成16進制是0x64,用二進製表示為:


0110 0100
  

將它跟1進行&操作,1的二進制為01,用ret表示運算結果,即


ret = num & 1
;
  

則ret的結果就是最後一位(bit 0),判斷ret是否為1,就是判斷最後一位是否為1。同理,2的二進制是10,通過語句


ret = num & 2
;
  

就能判別倒數第2位(bit 1),以此類推,0100應為4,即


ret = num & 4
;
  

用來判斷倒數第3位(bit 2)。這都是對1進行左移,即2的冪的關係。一個整型數是32位,使用循環語句從0循環到31即可以實現要求。下面給出參考程序及運行示範。


//
參考程序
#include <stdio.h>
int main
()
{
    int i=0
,num=0
,sum=0
;
    printf
(\"Input a num
:\"
);
    scanf
(\"%d\"
,&num
);
    for
(i=0
;i<32
;i++
)
    {
          if
(num&
(1<<i
)){
               printf 
( \"bit %d is 1.n\"
, i 
);
              sum++
;
          }
    }
    printf 
( \"num %d
(%#x
) has %d bit is 1.n\"
, num
,num
,sum 
);
    return 0
;
}
Input a num
:15
bit 0 is 1.
bit 1 is 1.
bit 2 is 1.
bit 3 is 1.
num 15
(0xf
) has 4 bit is 1.
Input a num
:
255
bit 0 is 1.
bit 1 is 1.
bit 2 is 1.
bit 3 is 1.
bit 4 is 1.
bit 5 is 1.
bit 6 is 1.
bit 7 is 1.
num 255
(0xff
) has 8 bit is 1.
Input a num
:
100
bit 2 is 1.
bit 5 is 1.
bit 6 is 1.
num 100
(0x64
) has 3 bit is 1.
  

【例17.11】統計一個數二進製表示中1的個數。

【解答】有很多問題只要知道二進制數中有幾個1,這在很多情況下還是很有用的。上例中的循環要經歷32次,效率是很低的。但它的好處是知道哪一位為1,現在既然不要求這一點,就可以採用效率高的方法求解。

假設有數n,它最右邊的i位是1,則n-1的第i位就是0,兩者進行與(&)操作,正好第i位的1被清除。例如0xa的二進制是1010,第bit 1位是1,0xa-1=0x9,即1001,兩者相與,(0x0a)&(0x0a-1)=0x8(1000)。則清除了0xa第bit 1位的1。

再用0x8&0x7,就把bit 3的1清0,而且0x8&0x7=0,即0xa有2個bit為1。

結論:一個數n與比它少1的數n-1進行與操作n&(n-1),就能清除數n最右邊的1。

驗證:0x6c&(0x6b)=0x68

  0110 1100

& 0110 1011

= 0110 1000

因為每次只清除最右邊的1而保留該位左邊的所有1,將n&(n-1)作為新的n,繼續做下去,依次為0x60、0x40、0,執行4次,統計出0x6c(十進制108)有4個位是1。

設sum為1的個數計數器,num=num&(num-1)循環到num=0為止,就求出1的個數。


sum=0
;  //1
的個數,循環到num
為0
,次數就是1
的個數
while
(num
!=0
)
{
     num=num&
(num-1
);
      sum++
;
}
  

對於256,它只要1個循環,效率很高,而for循環都是執行32次循環。這種算法最好情況是沒有1(不循環),最壞情況是全部是1(要循環32次)。

因為程序最後要用到num,所以使用它的副本temp,參考程序中輸出每次相與之後的結果以便看出執行過程加深理解。下面給出程序及運行示範。


//
參考程序
#include <stdio.h>
int main
()
{
     int num=0
, sum=0
, temp
;
     printf
(\"Input a num
:\"
);
     scanf
(\"%d\"
, &num
);
     temp=num
;
     while
(temp 
!= 0
)
     {
          temp=temp&
(temp-1
);
          printf 
( \"Now num = %#x n\"
, temp 
);
          sum++
;
     }
     printf 
( \"num %d
(%#x
) has %d bit is 1.n\"
, num
,num
,sum 
);
     return 0
;
}
Input a num
:
10
Now num = 0x8
Now num = 0
num 10
(0xa
) has 2 bit is 1.
Input a num
:
108
Now num = 0x68
Now num = 0x60
Now num = 0x40
Now num = 0
num 108
(0x6c
) has 4 bit is 1.
Input a num
:
256
Now num = 0
num 256
(0x100
) has 1 bit is 1.
Input a num
:
255
Now num = 0xfe
Now num = 0xfc
Now num = 0xf8
Now num = 0xf0
Now num = 0xe0
Now num = 0xc0
Now num = 0x80
Now num = 0
num 255
(0xff
) has 8 bit is 1.
Input a num
:
65535
Now num = 0xfffe
Now num = 0xfffc
Now num = 0xfff8
Now num = 0xfff0
Now num = 0xffe0
Now num = 0xffc0
Now num = 0xff80
Now num = 0xff00
Now num = 0xfe00
Now num = 0xfc00
Now num = 0xf800
Now num = 0xf000
Now num = 0xe000
Now num = 0xc000
Now num = 0x8000
Now num = 0
num 65535
(0xffff
) has 16 bit is 1.