在實際問題中, 有些變數的取值被限定在一個有限的範圍內。例如,一個星期內只有七天,一年只有十二個月, 一個班每周有六門課程等等。如果把這些量說明為整型, 字元型或其它類型顯然是不妥當的。 為此,C語言提供了一種稱為“枚舉”的類型。在“枚舉”類型的定義中列舉出所有可能的取值, 被說明為該“枚舉”類型的變數取值不能超過定義的範圍。應該說明的是, 枚舉類型是一種基礎資料型別 (Elementary Data Type),而不是一種構造類型, 因為它不能再分解為任何基本類型。
枚舉類型的定義和枚舉變數的說明
一、枚舉的定義枚舉類型定義的一般形式為:
enum 枚舉名
{ 枚舉值表 };
在枚舉值表中應羅列出所有可用值。這些值也稱為枚舉元素。
例如: enum weekday
{ sun,mou,tue,wed,thu,fri,sat };
該枚舉名為weekday,枚舉值共有7個,即一周中的七天。 凡被說明為weekday類型變數的取值只能是七天中的某一天。
二、枚舉變數的說明 如同結構和聯合一樣,枚舉變數也可用不同的方式說明, 即先定義後說明,同時定義說明或直接說明。設有變數a,b,c被說明為上述的weekday,可採用下述任一種方式:
enum weekday
{
......
};
enum weekday a,b,c;或者為: enum weekday
{
......
}a,b,c;或者為: enum
{
......
}a,b,c;
枚舉類型變數的賦值和使用
枚舉類型在使用中有以下規定:
1. 枚舉值是常量,不是變數。不能在程式中用指派陳述式再對它賦值。例如對枚舉weekday的元素再作以下賦值: sun=5;mon=2;sun=mon; 都是錯誤的。
2. 枚舉元素本身由系統定義了一個表示序號的數值,從0 開始順序定義為0,1,2…。如在weekday中,sun值為0,mon值為1, …,sat值為6。
main(){
enum weekday
{ sun,mon,tue,wed,thu,fri,sat } a,b,c;
a=sun;
b=mon;
c=tue;
printf("%d,%d,%d",a,b,c);
}
3. 只能把枚舉值賦予枚舉變數,不能把元素的數值直接賦予枚舉變數。如: a=sum;b=mon; 是正確的。而: a=0;b=1; 是錯誤的。如一定要把數值賦予枚舉變數,則必須用強制類型轉換,如: a=(enum weekday)2;其意義是將順序號為2的枚舉元素賦予枚舉變數a,相當於: a=tue; 還應該說明的是枚舉元素不是字元常量也不是字串常量, 使用時不要加單、雙引號。
main(){
enum body
{ a,b,c,d } month[31],j;
int i;
j=a;
for(i=1;i<=30;i++){
month[i]=j;
j++;
if (j>d) j=a;
}
for(i=1;i<=30;i++){
switch(month[i])
{
case a:printf(" %2d %c/t",i,'a'); break;
case b:printf(" %2d %c/t",i,'b'); break;
case c:printf(" %2d %c/t",i,'c'); break;
case d:printf(" %2d %c/t",i,'d'); break;
default:break;
}
}
printf("/n");
}
位元運算
前面介紹的各種運算都是以位元組作為最基本位進行的。 但在很多系統程式中常要求在位(bit)一級進行運算或處理。C語言提供了位元運算的功能, 這使得C語言也能像組合語言一樣用來編寫系統程式。
一、位元運算符C語言提供了六種位元運算符:
& 按位與
| 按位或
^ 按位異或
~ 取反
<< 左移
>> 右移
1. 按位與運算 按位與運算子"&"是雙目運算子。其功能是參與運算的兩數各對應的二進位相與。只有對應的兩個二進位均為1時,結果位才為1 ,否則為0。參與運算的數以補碼方式出現。
例如:9&5可寫算式如下: 00001001 (9的二進位補碼)&00000101 (5的二進位補碼) 00000001 (1的二進位補碼)可見9&5=1。
按位與運算通常用來對某些位清0或保留某些位。例如把a 的高八位清 0 , 保留低八位, 可作 a&255 運算 ( 255 的位元為0000000011111111)。
main(){
int a=9,b=5,c;
c=a&b;
printf("a=%d/nb=%d/nc=%d/n",a,b,c);
}
2. 按位或運算 按位或運算子“|”是雙目運算子。其功能是參與運算的兩數各對應的二進位相或。只要對應的二個二進位有一個為1時,結果位就為1。參與運算的兩個數均以補碼出現。
例如:9|5可寫算式如下: 00001001|00000101
00001101 (十進位為13)可見9|5=13
main(){
int a=9,b=5,c;
c=a|b;
printf("a=%d/nb=%d/nc=%d/n",a,b,c);
}
3. 按位異或運算 按位異或運算子“^”是雙目運算子。其功能是參與運算的兩數各對應的二進位相異或,當兩對應的二進位相異時,結果為1。參與運算數仍以補碼出現,例如9^5可寫成算式如下: 00001001^00000101 00001100 (十進位為12)
main(){
int a=9;
a=a^15;
printf("a=%d/n",a);
}
4. 求反運算 求反運算子~為單目運算子,具有右結合性。 其功能是對參與運算的數的各二進位按位求反。例如~9的運算為: ~(0000000000001001)結果為:1111111111110110
5. 左移運算 左移運算子“<<”是雙目運算子。其功能把“<< ”左邊的運算數的各二進位全部左移若干位,由“<<”右邊的數指定移動的位元,
高位丟棄,低位補0。例如: a<<4 指把a的各二進位向左移動4位。如a=00000011(十進位3),左移4位後為00110000(十進位48)。6. 右移運算 右移運算子“>>”是雙目運算子。其功能是把“>> ”左邊的運算數的各二進位全部右移若干位,“>>”右邊的數指定移動的位元。
例如:設 a=15,a>>2 表示把000001111右移為00000011(十進位3)。 應該說明的是,對於有符號數,在右移時,符號位將隨同移動。當為正數時, 最高位補0,而為負數時,符號位為1,最高位是補0或是補1 取決於編譯系統的規定。Turbo C和很多系統規定為補1。
main(){
unsigned a,b;
printf("input a number: ");
scanf("%d",&a);
b=a>>5;
b=b&15;
printf("a=%d/tb=%d/n",a,b);
}
請再看一例!
main(){
char a='a',b='b';
int p,c,d;
p=a;
p=(p<<8)|b;
d=p&0xff;
c=(p&0xff00)>>8;
printf("a=%d/nb=%d/nc=%d/nd=%d/n",a,b,c,d);
}
位域
有些資訊在儲存時,並不需要佔用一個完整的位元組, 而只需占幾個或一個二進位位。例如在存放一個開關量時,只有0和1 兩種狀態, 用一位二進位即可。為了節省儲存空間,並使處理簡便,C語言又提供了一種資料結構,稱為“位域”或“位段”。所謂“位域”是把一個位元組中的二進位劃分為幾個不同的地區, 並說明每個地區的位元。每個域有一個網域名稱,允許在程式中按網域名稱進行操作。 這樣就可以把幾個不同的對象用一個位元組的二進位位域來表示。一、位域的定義和位域變數的說明位域定義與結構定義相仿,其形式為:
struct 位域結構名
{ 位域列表 };
其中位域列表的形式為: 類型說明符 位網域名稱:位域長度
例如:
struct bs
{
int a:8;
int b:2;
int c:6;
};
位域變數的說明與結構變數說明的方式相同。 可採用先定義後說明,同時定義說明或者直接說明這三種方式。例如:
struct bs
{
int a:8;
int b:2;
int c:6;
}data;
說明data為bs變數,共佔兩個位元組。其中位域a佔8位,位域b佔2位,位域c佔6位。對於位域的定義尚有以下幾點說明:
1. 一個位域必須儲存在同一個位元組中,不能跨兩個位元組。如一個位元組所剩空間不夠存放另一位域時,應從下一單元起存放該位域。也可以有意使某位域從下一單元開始。例如:
struct bs
{
unsigned a:4
unsigned :0 /*空域*/
unsigned b:4 /*從下一單元開始存放*/
unsigned c:4
}
在這個位域定義中,a占第一位元組的4位,後4位填0表示不使用,b從第二位元組開始,佔用4位,c佔用4位。
2. 由於位域不允許跨兩個位元組,因此位域的長度不能大於一個位元組的長度,也就是說不能超過8位二進位。
3. 位域可以無位網域名稱,這時它只用來作填充或調整位置。無名的位域是不能使用的。例如:
struct k
{
int a:1
int :2 /*該2位不能使用*/
int b:3
int c:2
};
從以上分析可以看出,位域在本質上就是一種結構類型, 不過其成員是按二進位分配的。
二、位域的使用位域的使用和結構成員的使用相同,其一般形式為: 位域變數名·位網域名稱 位域允許用各種格式輸出。
main(){
struct bs
{
unsigned a:1;
unsigned b:3;
unsigned c:4;
} bit,*pbit;
bit.a=1;
bit.b=7;
bit.c=15;
printf("%d,%d,%d/n",bit.a,bit.b,bit.c);
pbit=&bit;
pbit->a=0;
pbit->b&=3;
pbit->c|=1;
printf("%d,%d,%d/n",pbit->a,pbit->b,pbit->c);
}
上常式序中定義了位域結構bs,三個位域為a,b,c。說明了bs類型的變數bit和指向bs類型的指標變數pbit。這表示位域也是可以使用指標的。
程式的9、10、11三行分別給三個位域賦值。( 應注意賦值不能超過該位域的允許範圍)程式第12行以整型量格式輸出三個域的內容。第13行把位域變數bit的地址送給指標變數pbit。第14行用指標方式給位域a重新賦值,賦為0。第15行使用了複合的位元運算符"&=", 該行相當於: pbit->b=pbit->b&3位域b中原有值為7,與3作按位與運算的結果為3(111&011=011,十進位值為3)。同樣,程式第16行中使用了複合位元運算"|=", 相當於: pbit->c=pbit->c|1其結果為15。程式第17行用指標方式輸出了這三個域的值。
類型定義符typedef
C語言不僅提供了豐富的資料類型,而且還允許由使用者自己定義類型說明符,也就是說允許由使用者為資料類型取“別名”。 類型定義符typedef即可用來完成此功能。例如,有整型量a,b,其說明如下: int aa,b; 其中int是整型變數的類型說明符。int的完整寫法為integer,
為了增加程式的可讀性,可把整型說明符用typedef定義為: typedef int INTEGER 這以後就可用INTEGER來代替int作整型變數的類型說明了。 例如: INTEGER a,b;它等效於: int a,b; 用typedef定義數組、指標、結構等類型將帶來很大的方便,不僅使程式書寫簡單而且使意義更為明確,因而增強了可讀性。例如:
typedef char NAME[20]; 表示NAME是字元數群組類型,數組長度為20。
然後可用NAME 說明變數,如: NAME a1,a2,s1,s2;完全等效於: char a1[20],a2[20],s1[20],s2[20]
又如:
typedef struct stu{ char name[20];
int age;
char sex;
} STU;
定義STU表示stu的結構類型,然後可用STU來說明結構變數: STU body1,body2;
typedef定義的一般形式為: typedef 原類型名 新類型名 其中原類型名中含有定義部分,新類型名一般用大寫表示, 以
便於區別。在有時也可用宏定義來代替typedef的功能,但是宏定義是由預先處理完成的,而typedef則是在編譯時間完成的,後者更為靈活方便。
本章小結
1. 枚舉是一種基礎資料型別 (Elementary Data Type)。枚舉變數的取值是有限的,枚舉元素是常量,不是變數。
2. 枚舉變數通常由指派陳述式賦值,而不由動態輸入賦值。枚舉元素雖可由系統或使用者定義一個順序值,但枚舉元素和整數並不相同,它們屬於不同的類型。因此,也不能用printf語句來輸出元素值(可輸出順序值)。
3. 位元運算是C語言的一種特殊運算功能, 它是以二進位位為單位進行運算的。位元運算符只有邏輯運算和移位元運算兩類。位元運算符可以與賦值符一起組成複合賦值符。如&=,|=,^=,>>=,<<=等。
4. 利用位元運算可以完成組合語言的某些功能,如置位,位清零,移位等。還可進行資料的壓縮儲存和並行運算。
5. 位域在本質上也是結構類型,不過它的成員按二進位位分配記憶體。其定義、說明及使用的方式都與結構相同。
6. 位域提供了一種手段,使得可在進階語言中實現資料的壓縮,節省了儲存空間,同時也提高了程式的效率。
7. 類型定義typedef 向使用者提供了一種自訂類型說明符的手段,照顧了使用者編程使用詞彙的習慣,又增加了程式的可讀性。