聚合數據類型
聚合數據類型能夠同時存儲超過一個的單獨數據。
C提供了兩種類型的聚合數據類型——數組和結構。
- 數組是相同類型的元素的集合,它的每個元素都是通過下標引用或指針間接訪問來選擇的。
- 數組元素可以通過下標來訪問,這只是因爲數組的元素長度相同。
- 結構也是一些值的集合,這些值稱爲它的成員。但一個結構的各個成員可能具有不同的類型。
- 由於結構中各個成員可能具有不同的類型,導致結構的成員可能長度不同,所以我們不能使用下標來訪問它們。
- 相反,每個結構成員都有自己的名字,它們是通過名字來訪問的。
結構變量的聲明
首先,我要給出在結構聲明中最重要的知識點:
在聲明結構時,必須列出它包含的所有成員,這個列表包括每個成員的類型和名字。
接下來,我們一步一步的分析結構變量的聲明方式以及它與其他數據類型變量的聲明方式的不同之處。
對於其他數據類型變量,我們的聲明一般如下所示:
數據類型 | 變量名; |
---|---|
int | a; |
double | b; |
char | c; |
int | *d; |
按照一般的思維,那我們對結構體變量的聲明應該如下所示:
struct var;
但是,我們前面說過——在聲明結構時,必須列出它包含的所有成員,這個列表包括每個成員的類型和名字。
從上面的聲明語句中,我們看不出結構變量var包含成員的信息。
所以,我們更新(改進)對結構體變量的聲明:
struct {
int a;
double b;
char c;
int *d;
} var;
此時,我們可以通過聲明語句知道,結構體變量var中包含四個成員變量——a,b,c,d以及它們的類型。
但是,這種聲明方式存在一種缺點——不同的聲明語句會導致結構體變量的類型不同!
struct {
int a;
char b;
float c;
} x;
struct {
int a;
char b;
float c;
} y[20] , *z;
第一個聲明語句創建了結構變量x,它包含三個成員a,b,c。
第二個聲明語句創建了結構變量y和z。其中,
y是一個數組,包含了20個結構。
z是一個指針,指向這個類型的結構。
但是,即使它們的成員列表完全相同,這兩個聲明語句聲明的變量被編譯器當作兩種截然不同的類型。
即,變量y和z的類型和x的類型不同。
所以,下面語句將是錯誤的:
z = &x;
所以,我們接着更新(改進)對結構體變量的聲明。
爲了保證包含相同成員的結構體聲明的變量的類型相同,C 語言增加了標籤字段tag。
標籤字段tag被視爲成員列表的一個等價名字。
標籤字段tag允許多個聲明使用同一個成員列表,並且創建同一種類型的結構變量。
例如,通過使用下列的語句,將標籤字段simple等價於後面花括號中各個變量的聲明。
struct simple{
int a ;
char b;
float c;
};
然後,我們可以使用simple代替成員列表,按照上述方法聲明變量。
struct simple x;
struct simple y[20] , *z;
不同的是,變量x,y,z是同一類型的結構變量。
最後,最優的方法,也是推薦的方法是使用關鍵字typedef。
關鍵字typedef的作用是爲一個數據類型定義一個新的名稱。
例如,通過下面語句,我們將結構體類型定義爲Simple。
typedef struct {
int a;
char b;
float c;
} simple;
然後,我們可以使用simple去定義結構體變量:
simple x;
simple y[20] , *z;
結構的自引用聲明
在一個結構體內部包含一個類型爲該結構本身的成員是否合法呢?
這個問題的核心在於——結構變量在聲明時,編譯器分配給它的內存空間的大小能否被確定!
通過下面兩個例子,我們進行敘述:
struct SELF_REF1{
int a;
int b;
struct SELF_REF1 c;
};
這種方式的自引用聲明是非法的!
這是因爲,成員c是一個完整的結構,其內部還將包含它自己的成員c,這個c就是另外一個完整的結構,它還將包含它自己的成員c。這樣重複下去永無止境,編譯器無法確定分配給它的內存空間大小。
struct SELF_REF2{
int a;
int b;
struct SELF_REF2 *c;
};
這種方式的自引用聲明是合法的!
指針變量的內存空間大小確定,編譯器可以知道分配給它的內存空間大小。
結構的不完整聲明
如何聲明一些相互之前存在依賴的結構,也就是說,其中一個結構包含了另外一個結構的一個或者多個成員呢?
如果每個結構都引用了其他結構的標籤,哪個結構應該首先聲明呢?
這個的問題的解決方案是使用不完整聲明。
不完整聲明的方式如下:
- 首先,聲明一個作爲結構標籤的標識符。
- 然後,我們可以把這個標籤用在不需要知道這個結構的長度的聲明中,例如聲明指向這個結構的指針。
- 最後,通過聲明語句把這個標籤與成員列表聯繫在一起。
struct B;
struct A{
struct B *partner;
};
struct B{
struct A *partner;
};
結構變量的初始化
結構的初始化方式和數組的初始化方式很相似。一個位於一個花括號{}內部,由逗號分隔的初始值列表可用於結構各個成員的初始化。這些值根據結構成員列表的順序寫出。如果初始列表的值不夠,剩餘的結構成員將使用默認值進行初始化。
結構中如果包含數組或結構成員,其初始化方式類似於多維數組的初始化。一個完整的聚合類型成員的初始值列表可以嵌套與結構的初始值列表內部。
typedef struct {
int a;
char b;
float c;
} simple;
struct INIT_EX{
int a;
short b[10];
simple c;
} x = {
10,
{1,2,3,4,5},
{25,'x',1.9}
};
結構成員的訪問
typedef struct {
int a;
char b;
float c;
} simple;
struct COMPLEX{
float f;
int a[20];
long *lp;
simple s;
simple sa[10];
simple *sp;
};
struct COMPLEX comp;
通過上述聲明,我們創建了一個結構變量comp。
直接訪問
結構變量的成員是通過點操作符.直接訪問的。
點操作符接受兩個操作數:
- 左操作數就是結構變量的名字
- 右操作數就是需要訪問的成員的名字
表達式的結果就是指定的成員。
例如:
我們通過表達式comp.a選擇了數組a這個成員,表達式的結果是個數組名,所以我們可以把它用在任何可以使用表數組名的地方。
我們通過表達式comp.s選擇了結構s這個成員,表達式的結果是個結構名,所以我們可以把它用在任何可以使用結構名的地方。所以,我們可以通過表達式comp.s.a選擇結構s的成員a。
那麼,comp.sa[4].c這個表達式的結果是什麼呢?
間接訪問
按照上述直接訪問的描述,如果我們擁有一個指向結構的指針,我們一般按照如下的方法訪問結構的成員:
- 首先,對指針執行間接訪問操作,獲得此結構。
- 然後,使用點操作符來訪問它的成員。
- 最後,由於點操作符的優先級高於解引用操作符的優先級,我們必須在表達式中使用括號,確保間接訪問首先執行。
所以,最後的表達式如下:
(*cp).f
由於我們很容易漏掉括號而導致錯誤,所以C語言通過提供了一種新的操作符來確保我們書寫的正確,那就是箭頭操作符->。
同樣,箭頭操作符也接受兩個操作數,左操作數必須是一個指向結構的指針。箭頭操作符對左操作數執行間接訪問取得指針所指向的結構,然後和點操作符一樣,根據右操作數選擇一個指定的結構成員。
那麼,上述表達式可以寫成:
cp -> f;