程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C >> C語言入門知識 >> C語言結構、聯合、位操作、位域和枚舉

C語言結構、聯合、位操作、位域和枚舉

編輯:C語言入門知識

1、結構

結構是派生的數據類型,可以使用其他數據類型來構造它們。

1.1 定義結構和結構類型的變量

關鍵字struct引入了結構定義,用一個標識符作為結構標記,來命名一個結構類型。結構定義大括號內聲明的變量是結構的成員。同一結構的成員必須具有具有獨一無二的名稱,但兩個不同的結構可能包含相同名稱的結構成員,而不會相互沖突。每個結構定義必須用分號結束。

結構標記與關鍵字struct一起用來定義結構類型的變量,也可以通過在結構定義的右大括號和結束結構定義的分號之間加入逗號分隔的變量名列表來定義結構變量。關鍵字typedef也提供了為前面定義的數據類型創建別名(或者同義詞)的機制。因此,也可以用typedef為結構類型建立比較短的類型名稱。typedef不會創建新類型只會為已經創建的類型定義了新的名稱,可以用作已創建類型的別名。

結構不能包含本身的實例,但是可以包含指向結構自身的指針,這裡稱為自引用結構。也就是說,在定義結構A的時候,不能有A類型的結構成員變量,但是可以有struct A *類型的成員變量(也就是說可以包含指向A類型的指針變量,也就是成員變量引用A自身,這很好理解吧)。需要注意的是結構的定義並沒有在內存中分配任何空間,而只是創建了用戶需要的用於定義變量的新的類型。定義結構類型變量的時候才會導致內存空間的分配。可以在結構上執行的操作有:將結構變量賦給相同類型的結構變量,獲得結構變量的地址(&),訪問結構變量的成員和使用sizeof運算符來確定結構變量的大小。

下面是幾種關於結構和結構變量的定義是等價的:

(1)
struct card
{
	char *face;
	char *suit;
};
struct card aCard,deck[52],*cardPtr;
(2)
struct card
{
	char *face;
	char *suit;
}aCard,deck[52],*cardPtr;
(3)
struct
{
	char *face;
	char *suit;
}aCard,deck[52],*cardPtr;
(4)
typedef struct
{
	char *face;
	char *suit;
}Card;
Card aCard,deck[52],*cardPtr;

可以看出結構標記是可選的,如果沒有結構標記,那麼就只能在結構定義的時候定義結構類型的變量。為了養成良好的編程習慣和風格,定義結構時提供結構標記還是很有必要的,能夠方便代碼中稍後定義的結構類型的變量。

1.2 結構的內存對齊問題

由於結構成員不一定是存儲在內存中的連續字節中,所以不能用==和!=來比較結構。有時候,因為計算機可能僅在某些內存邊界上存儲特定的數據類型,如半個字、字或者雙字(字是標准內存單元,它用於在計算機中存儲數據,通常是兩個字節或者四個字節)邊界,所以在存儲結構的內存區域中可能會有“洞”。這就是傳說中的內存對齊問題。

struct E 
{
	char c;
	int i;
}e1,e2;
上面的例子中,使用兩個字節作為字的計算機可能需要在字邊界上對齊struct E的每個成員,也就是在字的開頭處(這是和機器相關的)進行對齊。下圖中,說明了結構體成員變量在內存中的對齊情況,其中變量已經被賦值為字符‘a’和整數97。

\

如果成員存儲在字邊界的開頭處,則在類型struct E變量的存儲空間中有一個字節的空洞,如上圖中所示。空洞中的值是沒有定義的。如果e1和e2成員變量的值實際上相等,但可能因為在空洞中包含不同的值,所以結構比較並不一定相等。

1.3 結構的初始化

可以像數組那樣使用初始值列表來初始化結構。要初始化結構,需要在定義的結構變量名後面加入的等號,以及使用逗號分隔的初始化列表,並在外面加大括號。例如語句struct card aCard={"Three","Hearts"};在定義變量aCard是struct card類型的同時也將其成員face初始化為“Three”,suit初始化為"Hearts"。如果列表中初始值的個數小於結構成員的個數,則將把剩余的成員自動初始化為0,如果成員變量是指針,則初始化為NULL。如果在函數外部定義的結構變量沒有進行顯示地初始化,那麼其結構變量將初始化為0或者NULL。我們也可以在賦值語句中初始化結構變量:相同類型的結構變量之間賦值,或者對結構的單個成員進行賦值。

1.4 訪問結構成員

有兩個運算符可以用於訪問結構成員:結構成員運算符(.)和結構指針運算符(->)。結構成員運算符通過結構變量名來訪問結構成員,如printf("%s\n",aCard.suit);便可以訪問到aCard結構變量的成員suit;結構指針運算符通過結構指針來訪問結構成員,它由負號和大於號構成,兩個符號之間沒有空格,假設指針cardPtr是指向struct card的指針,那麼語句printf("%s\n",cardPtr->suit);便可以訪問到cardPtr所指向對象的suit成員。cardPtr->suit等價於(*cardPtr).suit。(由於結構成員運算符比解參考運算符的優先級要高,所以要加括號。)

1.5 在函數中使用結構

可以把單個結構成員、整個結構或者結構指針傳遞給函數。當把結構或者單個結構成員傳遞給函數時,它們采用的是值調用傳遞。所以,被調函數不能修改主調函數中結構的成員。要使用引用調用來傳遞結構,則需要傳遞結構變量的地址。結構數組和所有其它數組一樣,都是自動使用引用傳遞。應該知道的是,使用引用調用來傳遞結構要比使用值調用來傳遞結構效率要高,因為引用調用不用復制整個結構。

1.6 一個結構的例子

#include 
#include 
#include 

struct card
{
	const char *face;
	const char *suit;
};
typedef struct card Card;

void fillDeck(Card * const wDeck,const char *wFace[],const char *wSuit[]);
void shuffle(Card *const wDeck);
void deal(const Card * const wDeck);

int main()
{
	//定義數組存儲紙牌
	Card deck[52];
	//牌面數組
	const char *face[]={"Ace","Deuce","Three","Four","Five","Six","Seven","Eight","Nine","Ten","Jack","Queen","King"};
	//花色
	const char *suit[]={"Hearts","Diamonds","Clubs","Spades"};
	
	//設置隨機數發生器的種子
	srand(time(NULL));
	//load the card to deck
	fillDeck(deck,face,suit);
	//shuffle the card
	shuffle(deck);
	//distribute
	deal(deck);
	return 0;
}
void fillDeck(Card * const wDeck,const char *wFace[],const char *wSuit[])
{
	int i;
	for (i=0;i<=51;i++)
	{
		wDeck[i].face=wFace[i%13];
		wDeck[i].suit=wSuit[i%4];
	}
}
void shuffle(Card *const wDeck)
{
	int i;
	int j;
	Card temp;
	//隨機選出一張放在第i個位置
	for (i=0;i<=51;i++)
	{
		j=rand()%52;
		temp=wDeck[i];
		wDeck[i]=wDeck[j];
		wDeck[j]=temp;
	}
}
void deal(const Card *const wDeck)
{
	int i;
	for (i=0;i<=51;i++)
		printf("%5s of %-8s%c",wDeck[i].face,wDeck[i].suit,(i+1)%3?'\t':'\n');
}
運行結果:

\
這是一個關於發牌的例子,還是比較好的。首先定義紙牌結構的時候,成員變量的類型是const char *,這樣,便不能通過對該指針進行解參考來修改指針指向的變量的值,卻可以修改指針本身的指向,很明顯這樣是安全(符合一般程序設計中的最低權限原則)而且合理的。void fillDeck(Card * const wDeck,const char *wFace[],const char *wSuit[]);、void shuffle(Card *const wDeck);和void deal(const Card * const wDeck);等函數中const的運用十分巧妙的控制了函數對參數的訪問權限,十分漂亮。

2、聯合(union)<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA+waq6z8rH0rvW1sXJyfq1xMr9vt3A4NDNo6zL/NPrveG5ucDgJiMyMDI4NDujrMf4sfDU2tPawaq6z7XEs8nUsbmyz+3P4M2stcS05rSiv9W85KGj1NqzzNDy1tDT0NXi0fm1xMfpv/ajrMSz0Kmx5MG/1q685MrHz+C52LXEoaPV4tH5vs2/ydLUyrnTw8Gqus/AtLbU1eLQqbHkwb+1xMT0tOa/1bzkvfjQ0Lmyz+2jrMC0sdzD4rK7sdjSqrXExNq05r/VvOTAy7fRoaPBqrrPtcSzydSxv8nS1MrHyM7S4sDg0M2jrLTmtKLBqrrPy/nQ6NKqtcTX1r3ayv2x2NDr1sHJ2cTcubu05rSiwaq6z9bQ1by/1bzk1+6087XEs8nUsaGjtuDK/cfpv/bPwqOswaq6z7D8uqzBvdbWu/LBvdbW0tTJz7XEyv2+3cDg0M2jrMO/tM7Wu8Tc0v3Tw9K7uPazydSxo6zSsr7NysfWu8Tc0v3Tw9K71tbK/b7dwODQzaGjyrnTw8qxo6zTprjD16LS4tPD1f3It7XEwODQzcC00v3Tw8Gqus/W0LXEyv2+3aGjPC9wPgo8cD7U2sGqus/Jz7/J0tTWtNDQtcSy2df309Cjur2rwaq6z7izJiMyMDU0MDu4+M/gzazA4NDNtcTB7dK7uPbBqrrPoaK78cihwaq6z7XEtdjWtygmYW1wOynS1LywyrnTw73hubmzydSx1MvL47f7us294bm51rjV69TLy+O3+8C0t8POysGqus+zydSxoaPA4CYjMjAyODQ7tdijrNKysrvE3NPDPT26zSE91MvL47f7wLTAtLHIvc/BqrrPoaM8L3A+CjxwPjIuMSC2qNLlus2z9cq8u688L3A+CjxwPsGqus+1xLao0uW6zb3hubnA4CYjMjAyODQ7o6zPwsPmysfSu7j2wP3X06O6PC9wPgo8cD48cHJlIGNsYXNzPQ=="brush:java;">union number { int x; double y; };和struct一樣,上面僅僅是創建了一個類型,還沒有用該類型來定義變量。在聯合變量的定義中,僅能用於第1個聯合類型相同的值來初始化聯合,因為上面的例子中聯合成員的第一個類型是int,所以語句union number value={10};正確地初始化了變量value。但語句union number value={3.1415}將截斷浮點數值的小數部分,並通常會產生編譯警告。

2.2 聯合的例子

#include 
union number
{
	char c;
	unsigned int i;
};
int main()
{
	union number value;

	value.c='A';
	printf("%s:\nchar:%c\nunsigned int:%d\n","Put a value int the char member",value.c,value.i);

	value.i=97;
	printf("%s:\nchar:%c\nunsigned int:%d\n","Put a value int the unsigned int member",value.c,value.i);

	value.c='A';
	printf("%s:\nchar:%c\nunsigned int:%d\n","Put a value int the char member",value.c,value.i);
	return 0;
}
運行結果:

\
需要說明的是聯合這種數據結構可能不能輕易地移植到其它計算機系統上。聯合能否被移植通常依賴於給定系統上存儲聯合成員數據類型時所使用的對齊方式。

2.3 聯合的使用

從上面看,仿佛聯合這個東西是一個怪物,誰會自找麻煩去用這個啊。我也是這麼想的,結果在網上找到一篇文章專門寫聯合的使用的。摘了一寫東西,貼在這裡。

2.3.1 增加代碼的可讀性

struct  Matrix
{
    union
    {
        struct
        {
            float  _f11, _f12, _f21, _f22;
        };
        float  f[2][2];
    }_matrix;
};
struct  Matrix m;
該例子中,struct和float f[][]共享內存空間,沒有造成內存空間的浪費。這樣,用矩陣的時候可以用m._matrix.f(比如說傳參,或者是整體賦值等);需要用其中的幾個元素的時候可以用m._matrix._f11,可以避免用m.f[0][0](不直觀,且容易出錯)。

2.3.2 union和強制類型轉換

這裡需要說明的是union裡面的成員都是從低地址開始對齊的。拿上面定義的number聯合作為例子,其中的char成員c和unsigned int成員i,它們在內存中的分布應該如下圖:

\
下面是一個利用判斷大小端的例子,分別采用union和非union的實現:

#define TRUE 1
#define FALSE 0
#define BOOL int
//不用union
BOOL  isBigEndian()
{
    unsigned int  i = 1;   /* i = 0x00000001*/
    char  c = *(char  *)&i; /* 注意不能寫成 char c = (char)i; */
    return  (int )c != i;
}
//用union
BOOL  isBigEndian()
{
    union
    {
        unsigned int  i;
        char  c;
    }test;
    test.i = 2;
    return  test.c != 2;
}
3、位運算

在計算機內部是使用位序列來表示所有數據的。每一位的值可以是0或者是1。因此,利用位運算比較方便,而且通常比較高效。

3.1 C語言提供了的位運算符

&|^ 對兩個操作數按位進行與、或、異或操作
<<左移 將第一個操作數的各位向左移動第二個操作數所指定的位數;在右邊用0來填充空位,向左移動到邊界之外的1將丟失。
>>右移 將第一個操作數的各位向右移動第二個操作數所指定的位數;填充左邊的方法依賴於計算機。對於unsigned整數執行右移將使得左邊的空位用0代替,移動到右邊界之外的1將丟失。如果右邊的操作數是負值,或者右邊的操作數大於存儲左邊操作數的位數,則移位的結果是不確定的。
~取反 將操作數按位取反
3.2 一個例子:以二進制的形式輸出無符號整數

#include 
void displayBits(unsigned value);
int main()
{
	unsigned x;
	printf("Enter an unsigned integer:\n");
	scanf("%u",&x);
	displayBits(x);
	return 0;
}
void displayBits(unsigned value)
{
	unsigned c;
	unsigned displayMask=1<<31;
	printf("%10u= ",value);
	for (c=1;c<=32;c++)
	{
		putchar(value&displayMask?'1':'0');
		value<<=1;//左移一位
		if(c%4==0)
		{
			putchar(' ');
			if(c%8==0)
				putchar(' ');
		}
	}
	putchar('\n');
}
運行結果:

\
4、位域

在C語言中,可以指定結構或者聯合中unsigned或者int成員的位數,這些位稱為位域。位域在所需要的最小位數內存存儲數據,因此,可以更好地利用內存。位域成員必須聲明為int或者unsigned。

4.1 位域的聲明

struct bitCard
{
	unsigned face : 4;
	unsigned suit : 2;
	unsigned color : 1;
};
該定義包含3個unsigned位域,即face、suit和color,用於表示52張紙牌中的一張紙牌。在unsigned或者int成員名稱的後面加入冒號和表示位寬度的整數常量(也就是存儲成員所需的位數),就可以聲明位域。表示寬度的常量必須是0和系統上存儲int的總位數之間的一個整數。

struct example
{
	unsigned a : 13;
	unsigned   : 19;
	unsigned b : 4;
};
如上面的例子,我們也可以指定沒有命名的位域,其中的字段可以用來做填充內容。上面例子中沒有命名的19位作為填充內容,在這19位中不能存儲任何東西,只是用來保證成員b存儲在另一個存儲單元(這裡討論的是4字節的機器)。

struct example
{
	unsigned a : 13;
	unsigned   : 0;
	unsigned b : 4;
};
寬度為0的無名位域可以用於在新的存儲單元邊界上對齊下一個位域。上面的例子中使用沒有命名且寬度為0的位域來跳過存儲a的存儲單元中的剩余位(無論有多少),這使得b和下一個存儲單元邊界對齊。

4.2 使用位域的好處和缺點

因為位域沒有地址,所以不能用&來獲得位域的地址,另外,也不能像數組元素那樣訪問位域內的單個位。盡管位域可以節省空間,但是它們會使編譯器產生執行速度較慢的機器語言代碼。因為機器語言代碼與需要額外的步驟來訪問可尋址存儲單元中的部分。這是一種典型的時間換空間的實現。

4.3 例子

#include
struct bitCard 
{
	unsigned face : 4;
	unsigned suit : 2;
	unsigned color: 1;//1 bit
};
typedef struct bitCard Card;
void fillDeck(Card *const wDeck);
void deal(const Card * const wDeck);

int main()
{
	Card deck[52];
	fillDeck(deck);
	deal(deck);
	return 0;
}
void fillDeck(Card *const wDeck)
{
	int i;
	for (i=0;i<=51;i++)
	{
		wDeck[i].face=i%13;
		wDeck[i].suit=i/13;
		wDeck[i].color=i/16;
	}
}
void deal(const Card * const wDeck)
{
	int i;
	for (i=0;i<=51;i++)
		printf("Card:%2d Suit:%2d Color:%2d%s",wDeck[i].face,wDeck[i].suit,wDeck[i].color,(i+1)%3?" | ":"\n");
	printf("\n");
}
運行結果:

\
5、枚舉

C語言提供的最後一個用戶自定義類型稱為枚舉。由關鍵字enum聲明的枚舉是用標識符表示的一組整數常量。實際上,這些枚舉常量是可以自動設置值的符號常量。枚舉中的值從0開始,每次增加1(除非特別指定)。

5.1 枚舉聲明

如聲明enum months{JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC};創建了一個新類型enum months,其中標識符設置為整數0到11。如果要記錄月份1-12,可以采用enum months{JAN=1,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC};,第一個值被顯示地設置為1,後續的其余值從1開始累加,從而產生1到12的值。枚舉中的標識符必須是唯一的。通過標識符賦值,可以明確地在定義中設置枚舉的每個常量值。枚舉的多個成員可以具有相同的常量值。

5.2 例子

#include 
//聲明一個枚舉類型
enum months {JAN=1,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC};
int main()
{
	//定義一個枚舉變量
	enum months month;
	const char *monthName[]={"*Error*","January","February","March","April",
		"May","June","July","August","September","October","November","December"};
	for (month=JAN;month<=DEC;month=(enum months)(1+(int)month))
	{
		printf("%2d %10s%c",month,monthName[month],month%2?'\t':'\n');
	}
	return 0;
}
運行結果:

\
好了,又over了一篇。盡管充其量只能算是筆記,我還是像耐著性子把它們寫完,就算是鍛煉心態了。微笑

參考:http://blog.csdn.net/jiangnanyouzi/article/details/3158702和

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved