# 10.C Structures,Unions,Bit Manipulation and Enumerations
:::spoiler 分工
>蔡汶璇 10.1-10.7
>陳家儀 10.8-10.13
:::
## 10.1 Introdution & 10.2 Structure Definitions
* **結構(structure)**
是一種**衍生的資料型别(derived data type)**,與只能含有相同資料型別的陣列不同,結構可以含有許多==不同的資料型別變數==
* **結構定義**(看下圖)
```c=
struct employee{
char firstName[20];
unsigned int age[20];
char gender;
double hourlySalary;
};
```
>每一個結構定義後都必須以分號結束
> [name=小蜜蜂🐝]
1. **struct**是定義結構的關鍵字,而**card**則稱為**結構標籤(structure tag)**。與**struct**合在一起用來宣告該**結構型別(structure type)** 的變數。
2. 大括號裡面的則被稱為該結構的**成員(member)**,成員可以是多個不同型別的變數,結構內的成員若是兩個不同的型別其成員名稱可以是相同的。
3. 結構可以是聚合的,成員可以是陣列或是其他結構。
### 10.2.1 Self-Referential Structures
```c=
struct employee{
int age;
char gender;
struct employee sum; //ERROR
struct employee *sumPtr; // pointer
};
```
* 同樣的struct變數不能宣告在同一個struct型別中,但是一個指向struct型別的指標可以包含在struct型別裡。
* 若一個結構裡含有指向相同結構型別的指標成員,則此結構便稱為**自我參考結構(self-referential structure)**。
>結構不能包含他自己的實例
> [name=小蜜蜂🐝]
### 10.2.2 Defining Variables of Structure Types
* 結構變數的定義方式與其他型別定義方式依樣,在結構型別後直接宣告變數名稱。(如下)
```c=
struct card aCard, deck[52], *cardPtr;
```
* 結構型別變數宣告時也能放在定義的右大括號與分號之間,並以逗號隔開。(如下)
```c=
struct card{
char *face;
char *suit;
} aCard, deck[52], *cardPtr;
```
### 10.2.3 Structure Tag Names
* 結構標籤名稱可有可無。如果某結構定義不包含結構標籤名稱,則這種結構型別的變數就只能宣告在結構定義內,不能獨立宣告。
>在建立結構型別時,請務必給他一個結構標籤名稱,往後才能宣告這種結構型別的新變數。
> [name=小蜜蜂🐝]
### 10.2.4 Operations That Can Be Performed on Structures
* **結構可執行的合法運算**
1. 設定struct變數的值給另一個相同型別的struct變數
2. 取得struct變數的位置
3. 存取struct變數的成員
4. 使用sizeof運算子來決定struct變數大小
>將某一型別的結構指定給不同型別的結構,會產生編譯時期的錯誤。
> [name=小蜜蜂🐝]
* 結構是不能使用==和!=運算子,因為結構成員不一定會存放在連續的記憶體位元組中。
## 10.3 Initializing Structure
* **結構初始化**
在定義內的變數名稱後加上一個等號合大括號,大括號內以逗號隔開。
```c=
struct card aCard={"Three", "Hearts"};
```
* 如果初始值個數少於結構成員數量,則剩下成員自動初始化為0(指標則為NULL)。
## 10.4 Accessing Structure Member with . and ->
* **存取結構的成員運算子**
1. **結構成員運算子(structure member operator)**,也稱**點號運算子(dot operator)**。
```c=
printf("%s", aCard.suit);
```
2. **結構指標運算子(structure pointer operator)**,也稱為**箭號運算子(arrow operator)**。
```c=
printf("%s", cardPtr->suit);
```
3. 先對指標求值再用結構成員運算子來存取。
```c=
printf("%s", (*cardPtr).suit);
```
>請勿再->和.運算子的前後留下空白。消除這些空白可以幫忙凸顯包含此運算子的運萬是事實是上為單一的變數名稱。
>[name=小蜜蜂🐝]
>在結構指標運算子的-和>之間留下空白是一種語法錯誤。
>[name=小蜜蜂🐝]
>試圖只用成員名稱來參考結構的成員是一種語法錯誤。
>[name=小蜜蜂🐝]
>使用指標和結構成員運算子參考結構的成員時如果沒有加上小括號是一種語法錯誤。為預防此錯誤,程式使用(->)運算子來代替。
>[name=小蜜蜂🐝]
## 10.5 Using Structure with Functions
* **結構可用方式傳給函式**
1. 傳遞個別結構成員
2. 傳遞整個結構
3. 傳遞指向此結構的指標
* 當個別結構成員或整個結構成員被傳遞給函式時,會以傳值呼叫方式進行(會將整個結構複製一份),所以呼叫函式的結構成員部備受呼叫的函式更改。若想傳參考的方式來傳遞的話,則需要傳入此結構變數位址。
>誤以為結構和陣列一樣,會自動以傳參考呼叫來進行傳遞,然後試圖在受呼叫函式裡更改呼叫結構的值,這是一個邏輯錯誤。
>[name=小蜜蜂🐝]
>以傳參考呼叫來傳遞結構會比傳值呼叫更有效率。
>[name=小蜜蜂🐝]
## 10.6 typedef
* **typedef**關鍵字作用是對資料型別重新命名。
```
typedef 資料型別 新資料型別名稱;
```
* 使用typedef來重新命名struct。(如下)
```c=
typedef struct{
char *face;
char *suit;
} Card;
```
將此結構的資料型別重新命名(講白點就是給資料型別一個新名字),便可以使用下列方式來宣告變數。
```c=
Card deck[52];
```
* 有餘不同電腦資料型別名稱不盡相同,為配合機器運作只要修改typedef這一行即可提高程式的可攜性。
* typedef也有助於程式「自行文件化」。
>使用typedef可以讓程式更具可攜性。
>[name=小蜜蜂🐝]
>使用typedef可以幫助程式增加可讀性和可維護性。
>[name=小蜜蜂🐝]
## 10.7 Example:High-Performance Card Shuffling and Dealing Simulation
* 課本例子。(這裡只講洗牌的函式,剩下的detail各位就自己看了)

* 設置一個迴圈走訪52張牌,接著將陣列中目前的排與隨機選取的Card互換,並重複執行52次後就完成了。
```c=
void shuffle(Card *const wDeck){
for(size_t i = 0 ;i < 52; ++i){
size_t j =rand() % 52;
Card temp = wDeck[i];
wDeck[i] = wDeck[j];
wDeck[j] = temp;
}
}
```
## 10.8 Unions
Union 類似於 structure,是一種將不同 data types 儲存在**同一個記憶體空間**的特殊自訂型別。可以對一個結構裡面的元素,用不同的資料型態去理解
>struct 是每個成員變數都配置一段空間,union 則是共用一段記憶體空間
1. 用來儲存一個 union 所需的記憶體位元數,**至少要能放得下此 union 最大的成員**。
2. 同時間只有一個 union 的成員(即只有一種資料型別)可進行參考(reference)。,準確來說他們是共用一個記憶體區塊,所以改第一個值第二個值會同時更改。
```
union主要用來壓縮空間,如果某些資料不會同時被用到,就可以使用union。
```
>如果以某種型別將資料存到union裡,卻以另一種型別來參考該資料,則結果會隨系統不同而有所差異
>[name=小蜜蜂]
## 10.8.1 Union Declarations
**union 的宣告方式:**
```c=
union 資料名稱 {
資料型別 變數名稱;
⋮
(成員宣告列表)
};
```
- 通常會將 union 的定義放在程式的標頭檔內,再將它涵括到所有使用此 union 的原始檔中
>將 union 或 struct 放在任何函式外,都不會建立一個全域變數(global variable),**union 定義只是建立一種新的型別**
>[name=小蜜蜂]
## 10.8.2 Operations That Can Be Performed on Unions
- 將某個 union 指定給另一個相同型別的 union
- 取得一個 union 的 address (&)
- 使用**結構成員運算子**和**結構指標運算子**來存取 union 的成員
> Union 之間不能用 == 和 != 來比較
## 10.8.3 Initializing Unions in Declarations + 10.8.4 Union 範例
**在宣告時設定的初始值**,只能設定和 union 的第一個成員**相同型別**的值。
<font color="#E04321">⚠ 若設定的資料型別與 union 第一個成員不符,可能會導致資料被刪減或有些編譯器會發出警告。
</font>
如下例:
```c=
#include <stdio.h>
union Var{
char ch;
double num;
};
int main(){
union Var var = {'x'};
// 宣告時設定的初始值只能指定和第一個成員相同的型別(本例為char)
// ex. union Var var = {456.789}; 這句不行!
printf("var.ch = %c\n",var.ch);
printf("var.num = %.3f\n\n",var.num);// 內容是無效的
var.num = 456.789;
printf("var.ch = %c\n",var.ch); // 內容是無效的
printf("var.num = %.3f\n\n",var.num);
return 0;
}
```
```
輸出:
var.ch = x
var.num = 0.000
var.ch = �
var.num = 456.789
```
>儲存一個 union 的儲存空間是依系統而定的,但是其大小至少要足以容納 union 的最大成員。
>[name=小蜜蜂]
>一個 union 的可攜性好不好,取決於系統或處理器對此 union 成員的資料型別所進行的儲存對齊方式 (storage alignment requirements)。
>[name=小蜜蜂]
## 10.9 Bitwise Operator
位元運算子(bitwise operator)可以用來操作、比較一組或兩組變數內的位元,程式通常用 unsigned 來與位元運算子一併使用,位元運算子整理於下表。
|運算子|意義|說明|
|:--|:--|:--|
|&|位元 AND|若兩個變數對應的位元**皆為** 1,則經此運算子結果為 1|
|\||位元 inclusive OR|若兩個變數對應的位元**至少有一個**為 1,則經此運算子結果為 1|
|^|位元 exclusive OR|若兩個變數對應的位元**只有一個**為 1,則經此運算子結果為 1|
|<<|左移 (left-shift)|將第一個位元往左移數個位元,左移多少位元數由第二個運算元指定 ; 右邊以 0 補滿|
|>>|右移 (right-shift)|將第一個位元往右移數個位元,右移多少位元數由第二個運算元指定 ; 左邊空位的填充方式依不同機器而定|
|~|位元補數(bitwise compliement)|所有為 1 的位元都設成 0 ; 所有為 0 的位元都設成1<br>通常稱為位元替換(toggling the bit)|
## 10.9.1 Displaying an Unsigned integer in Bits
- 通常位元運算子AND會和一個稱為**遮罩**(mask)的變數一起使用。
- 由於 mask = 2³¹ 除了最左邊的位元為 1 之外,其他位元都會是 0,與 輸入數值 做 & 運算,只會留下最左邊位元為 0 或為 1 的結果,其他部份都被 mask 的 0 與 & 運算遮掉了,這就是「位元遮罩」。
```c=
#include<stdio.h>
void bin(unsigned int dec);
int main()
{
unsigned int a;
scanf("%u", &a);
bin(a);
}
void bin(unsigned int dec)
{
unsigned int mask = 1 << 31; // 設一個 mask (長度為 unsigned int 所佔記憶體大小)
printf("%u = ", dec);
for (unsigned int i = 1 ; i <= 32 ; i++)
{
if( dec & mask ) // 被 mask 裡的 0 遮掉的恆為 0
{
putchar('1');
}
else
{
putchar('0');
}
dec <<= 1; // 每次將變數左移一個 bit
if(i % 8 == 0)
{
putchar(' ');
}
}
printf("in bin\n");
}
```
```
輸入:10
輸出:10 = 00000000 00000000 00000000 00001010 in bin
```
>別將**邏輯 AND 運算子**(&&)和**位元 AND 運算子**(&)混用。
>[name=小蜜蜂]
## 10.9.2 Making Function displayBits More Gentic and Portable
將10.9.1 例子中的位元數(32)改成較靈活的寫法,而非寫死的一個數字,以適應不同的硬體。
>**符號常數 CHAR_BIT** ( 定義在<limits.h>中 ),用來表示一個位元組中的位元數,而使用 sizeof 可得到該型別所需的位元組數量。
>故可將上例用來表位元數的數字 31 以 **CHAR_BIT * sizeof(unsigned int) - 1** 做替換 ; 數字 32 以 **CHAR_BIT * sizeof(unsigned int)** 做替換
>[name=小蜜蜂]
## 10.9.3 Using the Bitwise AND, Inclusive OR, Exclusive OR and Complement Operators
課本p452 以例子(似10.9.1)實際顯示 AND , Inclusive OR , Exclusive OR , Complement Operators 的使用結果:
- 位元 AND 運算子( **&** )
|位元1|位元運算子|位元2|運算結果|
|:--:|:--:|:--:|:--:|
|0|&|0|0|
|1|&|0|0|
|0|&|1|1|
|1|&|1|1|
ex. **5** (in bin) **&** **6** (in bin) **=** **4** (in bin)
- 位元 inclusive OR 運算子( **|** )
|位元1|位元運算子|位元2|運算結果|
|:--:|:--:|:--:|:--:|
|0|\||0|0|
|1|\||0|1|
|0|\||1|1|
|1|\||1|1|
- 位元 exclusive OR 運算子( **^** )
|位元1|位元運算子|位元2|運算結果|
|:--:|:--:|:--:|:--:|
|0|^|0|0|
|1|^|0|1|
|0|^|1|1|
|1|^|1|**0**|
- 位元補數運算子( **~** )
|位元運算子|位元|運算結果|
|:--:|:--:|:--:|
|~|1|0|
|~|0|1|
## 10.9.4 Using the Bitwise Left- and Right- Shift Operators
課本p455 以例子(似10.9.1)實際顯示 Left- and Right- Shift Operators 的使用結果
- 左移運算子( **<<** )
- 將左運算元向左移數個位元,移多少位元由右運算子指定。
- 右邊空出來的位置補 0 ,超出左邊的位元丟棄。
ex.
     n = 00000000 00000000 **00000011** **11000000** = 960
                ←
 n << 8 = 00000000 **00000011** **11000000** 00000000 = 245760
- 右移運算子( **>>** )
- 將左運算元向右移數個位元,移多少位元由右運算子指定。
- 左邊空出來的位置補 0 ,超出右邊的位元丟棄。
>右移、左移運算中,若右運算元為負值,或右運算元大於左運算元的位數,則運算結果未定義。
>[name=小蜜蜂]
## 10.9.5 Bitwise Assignment Operators
每個二元位元運算子都有一個對應的位元指定運算子(bitwise assignment operators),用法同算數指定運算子。
> **&=** 、 **|=** 、 **^=** 、 **<<=** 、 **>>=**
下圖列出目前出現過的運算子的優先順序與結合性,優先順序為上至下遞減。

## 10.10 Bit Fields
在 strut 和 union 的宣告中的每個成員後加上" **: 數字** " 這個東西就叫位元欄位(bit fields),可以限制每個成員用來儲存的位元大小,以節省記憶體空間。
## 10.10.1 Defining Bit Fields
接 10.10
- 在成員名稱後加一個冒號( **:** )以及一個表示欄位寬度的整數( 0 ~ int 在系統中佔用位元數)。
- 指定的位元數為何根據各成員需要來決定。
範例如下:
```c=
struct bitcard{
unsigned int face : 4;
unsigned int suit : 2;
unsigned int color : 1;
};
```
## 10.10.2 Using Bit Fields to Represent a Card's Face, Suit and Color
範例:用結構中的位元欄位來表示一副撲克牌。
>face : 0 ~ 12 (4 bits)
>suit : 0 ~ 3 (2 bits)
>color : 0 ~ 1 (1 bit)。
```c=
#include <stdio.h>
#define CARDS 52
struct bitCard
{
unsigned int face : 4; // 4 bits; 0-15
unsigned int suit : 2; // 2 bits; 0-3
unsigned int color : 1; // 1 bit; 0-1
};
typedef struct bitCard Card; // new type name for struct bitCard 14
void fillDeck(Card * const wDeck); // prototype
void deal(const Card * const wDeck); // prototype
int main(void)
{
Card deck[CARDS]; // create array of Cards fillDeck(deck);
fillDeck(deck);
puts("Card values 0-12 correspond to Ace through King");
puts("Suit values 0-3 correspond Hearts, Diamonds, Clubs and Spades"); puts("Color values 0-1 correspond to red and black\n");
deal(deck);
}
//initialize Cards
void fillDeck(Card * const wDeck)
{
// loop through wDeck
for (size_t i = 0; i < CARDS; ++i)
{
wDeck[i].face = i % (CARDS / 4);
wDeck[i].suit = i / (CARDS / 4);
wDeck[i].color = i / (CARDS / 2);
}
}
// output cards in two-column format; cards 0-25 indexed with
// k1 (column 1); cards 26-51 indexed with k2 (column 2)
void deal(const Card * const wDeck)
{
printf("%-7s%-8s%-17s%-7s%-8s%s\n", "Card", "Suit", "Color", "Card", "Suit", "Color");
// loop through wDeck
for (size_t k1 = 0, k2 = k1 + 26; k1 < CARDS / 2; ++k1, ++k2)
{
printf("%3d\t%2d\t%2d\t\t",
wDeck[k1].face, wDeck[k1].suit, wDeck[k1].color);
printf("%3d\t%2d\t%2d\t\n",
wDeck[k2].face, wDeck[k2].suit, wDeck[k2].color);
}
}
```
```
輸出:
Card values 0-12 correspond to Ace through King
Suit values 0-3 correspond Hearts, Diamonds, Clubs and Spades
Color values 0-1 correspond to red and black
Card Suit Color Card Suit Color
0 0 0 0 2 1
1 0 0 1 2 1
2 0 0 2 2 1
3 0 0 3 2 1
4 0 0 4 2 1
5 0 0 5 2 1
6 0 0 6 2 1
7 0 0 7 2 1
8 0 0 8 2 1
9 0 0 9 2 1
10 0 0 10 2 1
11 0 0 11 2 1
12 0 0 12 2 1
0 1 0 0 3 1
1 1 0 1 3 1
2 1 0 2 3 1
3 1 0 3 3 1
4 1 0 4 3 1
5 1 0 5 3 1
6 1 0 6 3 1
7 1 0 7 3 1
8 1 0 8 3 1
9 1 0 9 3 1
10 1 0 10 3 1
11 1 0 11 3 1
12 1 0 12 3 1
```
>位元欄有助於減少程式所需的記憶體
>[name=小蜜蜂]
>位元欄的操作依機器各異
>[name=小蜜蜂]
>以存取陣列的方式來存取位元欄位內個別的位元為語法錯誤,位元欄位並非「位元陣列」。
>[name=小蜜蜂]
>&運算子不可用在位元欄位上,因為他們沒有address
>[name=小蜜蜂]
>使用位元欄位會使編譯器產生較慢的機器語言,因為需要額外的動作來存取一個可定址儲存單元內的部分位元。(這是常見的空間與時間做取捨的例子之一)
>[name=小蜜蜂]
## 10.10.3 Unnamed Bit Fields
- 匿名的位元欄位(unnamed bit fields)是用來調整格式,作為結構內的填補區域(padding),沒有任何資料可以存放在它指定的位元裡面,範例:
```c=
struct example
{
unsigned int a : 13;
unsigned int : 19; // 成員 b 存放到下一個儲存單元
unsigned int b : 4;
};
```
- 寬度為 0 的匿名位元欄位(unnamed bit field with a zero width)用來將下一個位元欄位調整在新的儲存邊界上。範例:
```c=
struct example
{
unsigned int a : 13;
unsigned int : 0; //用來跳過 a 所存放之儲存單元的所有剩餘位元
unsigned int b : 4;
};
```
## 10.11 Enumeration Constants
- 列舉由關鍵字 **enum** 定義。
```c=
enum Days
{
MON, TUE, WED, THU, FRI, SAT, SUN
};
```
```
本例中列舉成員被設定為 0 ~ 6
```
- 列舉成員的型別預設為 int ; (未指定時)列舉成員會被賦值為從 **0** 開始遞增的數字。
- 可以給列舉成員手動賦值任何**整數數值**作為列舉的起點。範例:
- 手動賦值的列舉成員也可以是**小數**或**負數**,接在後面未手動賦值的成員依序遞增 1。
```c=
enum Days
{
MON = 1, TUE, WED, THU, FRI, SAT, SUN
};
```
```
本例中列舉成員被設定為 1 ~ 7
```
- 同範圍的列舉中,各**識別字須唯一** ; 但**不同成員可以設定為相同的常數值**。
>在定義列舉後,將值設給列舉型別常數(enumeration constants)會造成語法錯誤。
>[name=小蜜蜂]
>只使用大寫字母作為列舉成員的名稱,可凸顯這些常數在程式的作用,並提醒你**列舉成員不是變數**。
## 10.12 Anonymous Structures and Unions
- C11 支援不具名的 struct 和 union,也就是說它們可以宣告而不需要資料名稱。
- 匿名 struct 和 union 可以用巢狀的放進具名的 struct 和 union 中,成為他們的成員,且可以像一般成員一樣被存取。
```c=
struct Example
{
int apple;
int lemon;
struct
{
int banana;
int orange;
};
};
```
對於 struct Example 的變數 example 而言,你可以這樣存取成員:
```
example.apple;
example.lemon;
example.banana;
example.orange;
```
## 10.13 Secure C Programming
- struct 的 CERT 指南
- EXPO3-C : struct 變數的大小不一定是其成員空間大小的總和,可以使用 **sizeof** 來計算整個 struct 變數所佔的位元個數。
- EXP04-C : struct 變數無法進行相等或不相等的比較,必須對**個別**成員做比較。
- DCL39-C : 額外且未被定義的位元組,可能包含之前使用該記憶體遺留的機密資料。封包這些資料以清除額外的位元組。
- typeof 的 CERT 指南
- DCL05-C : 使用 **typedef** 建立自我說明的型別名稱以增加可讀性。
- 位元操作(Bit Manipulation)的 CERT 指南
- INT02-C : 在比 int 型別小的值使用位元運算會造成無法預期的結果。
- INT13-C : 有號整數型別的位元運算結果因系統而異。因此**無號整數**型別才可使用位元運算。
- EXP46-C : 在條件運算式(?:)中使用 & 和 | 會導致不可預期的行為,因為它們不使用最小化求值(Short-circuit evaluation)
> Short-circuit evaluation : 只有當第一個運算數的值無法確定邏輯運算的結果時,才對第二個運算數進行求值。
- enum 的 CERT 指南
- INT09-C : 允許多個列舉常數具有相同值可能會有難以找出的邏輯錯誤,enum 的列舉常數最好具有不同的值。
###### tags: `程設好難` `學校門口大樹石頭下` `他的課就很好睡阿`
<style>
.navbar-brand::after { content: " × 老葉的程式設計"; }
</style>