C的結構體和其他數據形式

結構體

聲明結構體時其實並未創建真實的數據,也並未給數據分配空間,只是描述了它由哪些成員(member)組成

struct tag {

  ...

};

也忽略tag可以直接聲明一個結構體變量,但這個結構體只能使用一次:

struct {

  ...

} var_name;


結構體的初始化,注意結構體中不同值之間用逗號(,)隔開,有兩種方法:

1. 按照順序初始化:

struct book b1 {
   "The Hours",
   "Michael Cunningham",
   3.99
};

2. 通過變量名初始化,可以只初始化某幾個變量:

struct book b1 = {
   .value = 3.99,
   .author = "Michael Cunningham",
   .title = "The Hours"};

也可以一次初始化多個結構體,不同結構體之間用逗號隔開

如果是static storage duration,變量只能用常量初始化(其實所有的static storage duration變量都是這樣)


結構體可以直接互相賦值,如struct book b2 = b1;

結構體可以作爲函數的返回值

結構體可以嵌套,如:

struct name {
   char first[20];
   char last[20];
};
struct person {
   int id;
   struct name pn;
};
struct person tom = { 123132, {"Tom", "Hanks"}};


結構體指針:

struct book* p; p = &b1;

注意,結構體的名字並不是結構體的地址(不像數組)。(*p).title == p->title。這裏因爲title是一個array,所以其實p->title的結果其實是數組名,也就是這個數組的地址


結構體參與函數運算有兩種方式:

1. 使用結構體指針,如:double sum(const struct* book); 這種方式在調用結構體時要使用結構體的地址,不能使用結構體的名字!如:sum(&b1);

2. 將結構體作爲參數,如double sum(struct book);

兩種方式的比較:

1. 結構體指針

優點:a) 能夠在舊的環境下運行 b) 速度快(因爲不用複製結構體本身,只需要傳遞地址)

缺點:對數據的保護不夠,可能會改動數據。但是這個問題可以通過將函數形參定義成const來解決

2. 傳送結構體作爲參數

優點:a) 函數處理的是結構體的副本,可以保護數據 b) 程序style更清晰

缺點:a) 舊的環境可能不支持 b) 速度慢,佔用更多的空間,特別是將一個大的結構體傳給函數卻只用到了其中的一兩個member

所以,這種方法通常只用於比較小的結構體


字符數組和結構體中的字符指針

struct names{
   char first[20];
   char last[20];
};
struct pnames{
   char* first;
   char* last;
};

對於names來說,字符串被存儲在結構體中;而對panmes來說,字符串被存放在compiler存放字符串常量的位置。pnames沒有給字符串分配存儲空間,所以它只能處理那些在別處已經被分配了空間的字符串(如字符串常量或字符串數組)。同時,如果給結構體中未初始化的字符指針賦值,如scanf("%s", pn1.last); 程序可能將輸入的字符串存儲在任何位置,而這可能會導致嚴重的問題。

如果使用malloc()來存儲結構體中的字符串,則可以在結構體中使用char型指針。


複合文字用於結構體(compound literal)

C99開始支持的複合文字(compound literal)除了數組外還支持結構體,如果只需要一個臨時結構體的值的話可以使用,如作爲實參傳入函數(如果函數的形參是一個地址,則需要將compound literal的地址傳入,在前面加上"&"即可)或給另一個結構體賦值。語法是將具體的初始值用大括號({})括起,在前面放置用小括號括起的值的類型,例:(struct book) {"The Hours", "Michael Cunningham", 3.99}(參考"C的數組與指針")


Flexible Array Members(C99)

C99支持一個叫做flexible array member的特性,它允許聲明一個結構體,這個結構體的最後一個member是一個數組,這個數組有以下特性:

1. 這個數組並不存在,至少不是立刻存在

2. 可以像這個數組存在一樣使用它,並且它的元素個數是任意的

聲明flexible array member要遵守以下規則:

1. flexible array member必須是結構體的最後一個member

2. 除它本身之外結構體必須要有其他的member

3. flexible array member的聲明方式與普通數組相同,但是括號內必須是空的

例:

struct flex {
   int count;
   double average;
   double scores[];   // flexible array member
};

儘管定義了結構體flex,但不能使用數組scores,因爲並未給它分配空間。要想使用這個結構體必須聲明一個指向它的指針並且使用malloc()爲flex的普通內容和flexible array member分配空間。例:

struct flex* pf; 
pf = (struct flex*)malloc(sizeof(struct flex) + 5*sizeof(double));

有flexible array member的結構體一些特殊的處理要求:

1. 例:struct flex* pf1, pf2; 

            ... 

            *pf2 = * pf1; 

這樣只能將除flexible array member外其他的普通member賦給*pf2,所以請使用memcpy()函數進行賦值(參考C預處理器和C函數庫)

2. 不要將這種結構體用於將結構體作爲傳入值的函數,原因與1相同;請使用傳入結構體地址的函數進行處理

3. 有flexible array member的結構體不能組成數組,也不能作爲其他結構體的成員


Anonymous Structure(C11)

anonymous structure是未命名的結構體成員,常用於union嵌套(nested union)

例:

struct name {                                   struct person {

   char first[20];                                   int id;  

   char last[20];                                    struct {char first[20]; char last[20];};

};                                       ==          };

struct person {

   int id;

   struct name pn;

};



Union

union能夠在相同的存儲空間中存儲不同的數據類型(但不是同時),通常的用途是存儲許多不規律或預先不知道的數據類型,另一個用途是當結構體中的某個數據的類型是什麼取決於其他member時。通過使用union數組可以創建一個單位大小相同的數組,每一個單位都可以存儲不同類型的變量,並且每一個單位都能夠以其他單位的形式進行訪問和讀取

union的創建很像結構體,有union模板和union變量,如:

union hold {
   int digit;
   double bigfl;
   char letter;
};

hold這個union可以存儲一個int或一個double或一個char型變量(只能儲存一個!),編譯器會分配能夠存儲佔空間最大的變量所需的空間給一個union變量。union的初始化有四種方法:a) 直接對union中的某個變量進行初始化 b) 使用一個union變量去初始化另一個同類型的union變量 c) 初始化union的第一個元素 d) 使用designated initializer(C99支持)

例:

union hold val_a;
val_a.letter = 'R';
union hold val_b = val_a;
union hold val_c = {88};
union hold val_d = {.bigfl = 118.2};       // designated initializer

以val_a爲例,它使用了char類型,但依然可以通過val_a.digit來訪問val_a存儲的內容(即將以char的形式存儲的內容用int型的方式進行訪問和讀取)


union的使用方式:

1. 使用點(.):union hold fit; fit.digit = 23;

2. 使用"->":union hold* pu; pu = & fit; x = pu->digit;


Anonymous Union(C11)

anonymous union和anonymous structure用法類似,就是說anonymous union是一個結構體或union的未命名member



枚舉類型(Enumerated Types)

可以使用枚舉類型聲明象徵性的名字來代表整型常量,通過使用關鍵詞enum可以創建新“類型”並且指明他們可能有的值(實際上,enum型常量就是int型)。使用枚舉值的目的是增強程序的可讀性,它的語法和結構體相似:

enum spectrum {red, orange, yellow, green, blue, violet};

enum spectrum color;

"red"等這些象徵性的常量叫做枚舉成員(enumerator),他們本質上就是int常數,只是"red"變成了代表整數0的命名了的常數,可以在任何可以使用整數常量的地方使用枚舉常量。要使用這些常量,如red,必須輸入數字1而不是字符串"red"。枚舉常量默認從0開始,也可以使用其他整數值來代替,如:

enum feline { cat, lynx = 10, puma, tiger};   // cat == 0, lynx == 10, puma == 11, tiger == 12

儘管枚舉成員(如"red")是int型,但枚舉變量並不嚴格地侷限於int型,只要可以容納枚舉的常量即可(比如也可以是unsigned char型)


有些C下的枚舉性質並不能移植到C++,比如C允許對枚舉變量使用"++"而C++則不允許。所以如果想讓程序兼容C++就儘量少使用枚舉類型


共享命名空間(shared namespace)

C用命名空間(namespace)來指明程序中名字可以被識別的部分。在一個scope中的structure tag,union tag,enumeration tag共享同樣的命名空間,這個命名空間和普通變量的不同。也就是說,一個變量名在一個scope中可以同時用於變量和tag而不會產生問題。但是強烈不建議這麼做,因爲會產生混淆,並且C++也不允許這樣,因爲它把所有的tag和變量名放在同一個namespace中


typedef

typedef是一個能讓你爲一個類型(type)創建新名字的高級數據特性,它類似#define,但是有三點不同(注意typedef最後要接分號):

1. 不像#define,typedef只能給類型(type)命名而不能給值命名

2. typedef的實現是由compiler完成的,不是預處理器

3. 在這些限制內時,typedef比#define更靈活

#define UCHAR unsigned char == typedef unsigned char UCHAR;

但是typedef char* STRING;,不能用#define STRING char*實現。如果使用前者,STRING name, sign; == char* name, *sign;,name和sign都是指針;而如果使用後者,STRING name, sign; == char* name, sign;,只有name是指針,而sign是char型


例:typedef unsigned char UCHAR;

這個定義的作用域取決於typedef語句的位置


Fancy Declarations

"[]"代表數組,"()"代表函數,"*"代表指針

一些符號的優先級:

1. "[]"和"()"有同樣的優先級,比"*"的優先級高,所以int* risk[10]先是一個數組,然後它的類型是int*

2. "[]"和"()"的順序是從左到右,因此int good[12][50]先是一個長度爲12的數組,然後每個元素又是長度爲50的int型

3. 因爲"[]"和"()"的優先級相同且是從左到右,所以int (* rusk)[10]先是一個指針,然後這個指針指向了一個長度爲10的int型數組

根據上面的理論可得:

char* fump(int):是一個函數;函數名是fump,參數是int型,返回值是char*

char (* frump)(int):是一個指針,指針名是frump,指向一個函數;函數的參數是int型,返回值是char型

char (* flump[3])(int):是一個數組,數組名是flump,長度爲3;數組的元素是指針,指向一個函數;函數的參數是int型,返回值是char型

typedef可以用來簡化上面的定義,例:

typedef int arr5[5];
typedef arr5 * p_arr5;
typedef p_arr5 arrp10[10];
arr5 togs;   // togs是一個長度爲5的int型數組
p_arr5 p2;   // p2是一個指向長度爲5的int型數組的指針
arrp10 ap;   // ap是一個長度爲10的數列,數列的元素是指針,指針指向長度爲5的int型數組



函數指針

指針可以指向函數,函數指針通常用作另一個函數的參數,告訴這個“另一個函數”應該調用什麼函數。函數指針也是存放一個地址,這個地址標誌了一段函數的開始代碼。當聲明函數指針時,必須要指明所指向函數的類型。函數的類型由函數簽名(signature)決定,即函數的參數類型和返回值,例如【void (*pf)(char *)】就聲明瞭一個函數指針,這個函數的參數是char*,返回值爲空。確定一個函數指針格式的快速方法是將函數的prototype中的函數名用指針(*pf)代替即可,注意要有括號,如果沒有括號【void *pf(char*)】是一個函數,函數名是pf,參數是char*,返回值是pointer-to-void(參考前面fancy declaration部分)

聲明瞭函數指針後,就要將同類型函數的地址賦給它。在這種情況下,函數名就可以代表函數的地址

例:

void ToUpper(char *);
void (*pf)(char *);
pf = ToUpper;
char mis[] == "Nina"
(*pf)(mis);       // 合法,因爲pf指向ToUpper函數,所以*pf是ToUpper函數
pf(mis);           // 合法,因爲函數名是一個指針,所以指針名和函數名可以互換


函數指針的一個常見用法是作爲另一個函數的參數,例:

void show(void (* fp)(char *), char* str); 
... 
show(ToUpper, mis);
show(pf, mis);


函數不能定義成數組,但是函數指針可以定義成數組


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章