# 程式語言 - 林佳志
[toc]
## Chapter 1: PRELIMINARIES
### 語言評估標準(四大指標)
* Readability:程式易讀
* Writability:程式易寫
* Reliability:程式可靠(符合規格)
* Cost:總成本(訓練、撰寫、維護、執行)
#### Readability(可讀性)
* **簡潔性**:功能集合不過多
* **最少重複功能**:例如 C++ 中有 `count++`, `++count`, `count+=1` 都是一樣 → 反而變複雜
* **最少運算子多載**
* **正交性(Orthogonality)**:少量基本元素能以一致方式組合
* **資料型別完整、語法一致、自我解釋**
  例:C 用 `{}`、Ada 用 `if ... end if`,後者更清楚。
#### Writability(可寫性)
* 簡單一致(語法不多卻能表達豐富)
* 支援抽象化(例如函式、類別)
* 表達能力強(多種運算子、內建函式)
#### Reliability(可靠性)
* **型別檢查**:避免型別錯誤
* **例外處理**:錯誤可被攔截處理
* **別名(Aliasing)**:同一記憶體不同名稱會降低可靠性
* **可讀/可寫性** 會影響可靠性:語言太怪,容易出錯。
#### 成本與其他標準
* 成本包含訓練、編譯、執行、維護
* 可靠性差會提高成本
* 其他標準:
  * **可移植性(Portability)**:不同平台能運作
  * **通用性(Generality)**:應用範圍廣
  * **明確性(Well-definedness)**:定義精確完整
#### 電腦架構與語言設計
* **主要影響:Von Neumann Architecture**
  * 記憶體儲存資料與指令
  * CPU 與記憶體間傳資料
  * 對應語言:Imperative(命令式語言)
  * 例:變數像記憶體格子,迴圈像重複取指令
* **瓶頸(Von Neumann Bottleneck)**:CPU比記憶體快太多 → 傳輸速度成限制
### 程式設計方法論影響
* 1950s–60s:效率導向 → FORTRAN
* 1970s:可讀性導向 → 結構化程式設計(C)
* 1980s:資料導向、物件導向(OOP)
### 語言類別
* **Imperative**:命令式(C、Java)
* **Functional**:函數式(LISP、F#)
* **Logic**:邏輯式(Prolog)
* **Markup/Hybrid**:標記與程式混合(HTML+JSTL)
### 語言設計取捨
* **可靠 vs 執行效率**(Java陣列檢查安全但慢)
* **可讀 vs 可寫**(APL語法短但難讀)
* **靈活 vs 可靠**(C++指標強但危險)
### 語言實作方法
1. **編譯(Compilation)** → 翻成機器碼 → 快
2. **直譯(Pure Interpretation)** → 一行一行執行 → 慢
3. **混合(Hybrid)** → 先轉中介碼再執行(例:Java bytecode)
* **JIT (Just-In-Time)** → 執行時才編譯,提高效率
* **Preprocessor (前置處理)** → 如 `#include`, `#define`
### 程式開發環境
* **UNIX**:早期系統 + 工具集合
* **Visual Studio .NET**:適用 C#、VB.NET
* **NetBeans**:Java 的開發環境
## Chapter 3: DESCRIBING SYNTAX AND SEMANTICS
### 語法與語意的基本概念
* **Syntax(語法)**:程式的形式結構 → 決定語言中「哪些寫法是合法的」。
* **Semantics(語意)**:程式的意義 → 決定「程式實際執行會做什麼」。
* 語法與語意的定義可供以下人使用:
  * 語言設計者
  * 編譯器實作者
  * 程式設計師(理解程式運作)
### 語法描述的基本名詞
* **Sentence(句子)**:由字母表組成的字串
  例:`int sum = a + b;`
* **Language(語言)**:句子的集合
  例:所有合法的 C 程式句子。
* **Lexeme(字素)**:語言中最小的語法單位
  例:`int`、`sum`、`=`
* **Token(記號)**:字素的分類
  例:`int` 屬於「關鍵字 token」、`sum` 屬於「識別字 token」
### 語言的形式定義方式
* **Recognizer(辨識器)**
  * 判斷輸入的字串是否屬於語言
  * 例:編譯器中的語法分析器(Syntax Analyzer)
* **Generator(產生器)**
  * 能夠「生成」語言中合法的句子
  * 透過比對產生規則可檢查語法是否正確
### BNF 與 Context-Free Grammar(上下文無關文法)
* **Context-Free Grammar (CFG)**:
  由 Noam Chomsky 提出,用來描述自然或程式語言結構。
* **BNF (Backus-Naur Form)**:
  John Backus 為 ALGOL 語言設計的描述語法方法。
* **BNF 與 CFG 等價**:都是用來產生語言結構的「產生規則」。
#### 範例
```bnf
<expression> → <identifier> = <number> + <number>
<identifier> → sum
<number> → 3 | 4
```
對應的程式:`sum = 3 + 4;`
### BNF 的基本元素
* **非終端符號 (Nonterminal)**:
  用於代表語法結構類別,如 `<expr>`、`<stmt>`
* **終端符號 (Terminal)**:
  語言中實際出現的符號或字
* **產生式 (Rule)**:
  `<左邊> → <右邊>`,表示如何組合語法結構
#### 範例
```bnf
<assignment> → <identifier> = <expression>
<identifier> → x | y | z
<expression> → <identifier> + <identifier> | <identifier>
```
### 語法推導(Derivation)
* 從 **起始符號 (Start Symbol)** 開始
* 重複套用產生規則直到只剩下終端符號
* 得到的字串稱為 **句子 (Sentence)**
#### 範例
```bnf
<stmt> → <var> = <expr>
<var> → a | b | c
<expr> → <term> + <term>
<term> → const | <var>
```
推導過程:
```
<stmt>
→ a = <expr>
→ a = <term> + <term>
→ a = b + const
```
結果:`a = b + const`
### 左推導與右推導
* **Leftmost derivation**:每次展開最左邊的非終端符號
* **Rightmost derivation**:展開最右邊的非終端符號
* 同一語法可能有多種推導方式
#### 範例:
```
<expr> → <term> + <term>
<term> → <var>
<var> → a | b
```
左推導:`<expr> → a + b`
右推導:`<expr> → a + b`
→ 結果相同,但推導順序不同。
### Parse Tree(語法樹)
* 用階層式方式表示推導過程
* 根節點是起始符號
* 葉節點是最終的終端符號
範例:`a = b + const`
```
<program>
 └─ <stmts>
     └─ <stmt>
         ├─ <var> → a
         ├─ =
         └─ <expr>
             ├─ <var> → b
             ├─ +
             └─ const
```
### 模糊性(Ambiguity)
* 一個文法若能產生「相同句子但有兩棵不同語法樹」,即為 **模糊文法**。
  → 對編譯器而言會造成「語法歧義」。
#### 範例:
```
<expr> → <expr> <op> <expr> | const
<op> → / | -
```
句子:`const - const / const`
可能有兩種解讀方式,因此是模糊的。
### 消除模糊:運算子優先與結合性
* **修正版文法**
  ```
  <expr> → <expr> - <term> | <term>
  <term> → <term> / const | const
  ```
  → 明確定義了減法比除法的「外層結構」。
* **結合性(Associativity)**
  * 左結合:`(a + b) + c`
  * 右結合:`a + (b + c)`
#### 範例:
```bnf
<expr> -> <expr> + const | const  // unambiguous
```
### if-then-else 模糊與解法
模糊例:
```bnf
<stmt> -> if (<expr>) <stmt> | if (<expr>) <stmt> else <stmt>
```
→ `if (x>0) if (y>0) z=1 else z=-1`
會不確定 else 屬於哪個 if。
✅ **解法:區分 matched / unmatched**
```bnf
<stmt> -> <matched> | <unmatched>
<matched> -> if (<cond>) <stmt> else <matched> | <other_stmt>
<unmatched> -> if (<cond>) <stmt> | if (<cond>) <matched> else <unmatched>
```
### EBNF(Extended BNF)
EBNF 是 BNF 的改良版,讓語法更簡短清晰。
| 符號    | 功能      | 範例      |
| ----- | ------- | ----------- |
| `[ ]` | 可選      | `<proc_call> → ident [(<expr_list>)]` |
| `( )` | 選擇      | `<term> → <term> (+ \| -) const` |
| `{ }` | 重複0次或多次 | `<ident> → letter {letter \| digit}`   |
#### 對比範例
BNF:
```bnf
<expr> → <expr> + <term> | <expr> - <term> | <term>
```
EBNF:
```bnf
<expr> → <term> {(+ | -) <term>}
```
### 靜態語意(Static Semantics)
* 指「與語法有關但不屬於句子結構」的規則
* 例如:
  * 變數必須先宣告後使用
  * 運算元類型要相容
* CFG(上下文無關文法)無法完全描述這些 → 需額外機制
### Attribute Grammar(屬性文法)
* 在文法規則中加入屬性(Attribute)與規則(Rule)
  → 幫助描述靜態語意檢查。
* 每個節點有:
  * **Synthesized Attribute(自下而上傳遞)**
  * **Inherited Attribute(自上而下傳遞)**
#### 範例
```
<assign> → <var> = <expr>
<expr> → <var> + <var> | <var>
<var> → A | B | C
```
屬性:
```
<expr>.actual_type ← <var>[1].actual_type
<expr>.expected_type ← <assign>.expected_type
Predicate:
<var>[1].actual_type == <var>[2].actual_type
```
### 語意描述的三種方法
| 方法                         | 概念           | 特點        |
| -------------------------- | ------------ | --------- |
| **Operational Semantics**  | 模擬程式實際執行步驟   | 直觀但複雜   |
| **Denotational Semantics** | 用數學函數描述狀態變化  | 抽象、可證明正確性 |
| **Axiomatic Semantics**    | 用邏輯斷言驗證程式正確性 | 用於程式驗證    |
### Operational Semantics(操作語意)
* 以「執行過程」定義語意
* 虛擬機模擬記憶體與指令變化
* 適用於教學與語言手冊說明
#### 例:
```
if (x > 0) then y := 2 else y := 3
```
狀態變化:
```
{x=1, y=0} → {x=1, y=2}
```
### Denotational Semantics(指稱語意)
* 由 Scott 與 Strachey 提出
* 用數學函數映射語法 → 意義
* 對每個語言結構定義「對應的數學對象」
* 可用於證明程式正確性
#### 範例:
```
Me(2 + 3, s) = 5
Me(x + y, s) = s(x) + s(y)
```
若 `s = {x=2, y=3}`,則結果 `5`
### Axiomatic Semantics(公理語意)
* 使用邏輯斷言(Assertions)描述程式執行前後的狀態
* 常用於**程式驗證(Program Verification)**
#### 格式:
```
{P} statement {Q}
```
P:前置條件
Q:後置條件
意思是:「如果在執行 S 之前,條件 P 成立,那執行完後,條件 Q 一定成立。」
#### 範例:
```
x := x + 1
Postcondition: x > n
Weakest Precondition: x = n
```
#### Weakest Precondition
有時候我們知道要的 結果 (Q),
但還不知道執行前要滿足什麼條件 (P)。
這時就用 Weakest Precondition 來「推回去」。
範例:
```
x := x + 1
Postcondition: x > 5
```
要保證結果成立,我們要找出最弱的條件(即「剛好足夠」的前提):
```
Weakest Precondition: x > 4
```
因為只要在執行前 x > 4,執行後 x + 1 > 5 一定成立。
### 主要公理與推論規則
* **Assignment Rule**
  `{Q[x→E]} x := E {Q}`
  → 在 Q 中將 x 取代為 E
* **Sequence Rule**
  `{P1} S1 {P2}`,`{P2} S2 {P3}` ⇒ `{P1} S1; S2 {P3}`
* **Selection Rule**
  `{B ∧ P} S1 {Q}`, `{¬B ∧ P} S2 {Q}` ⇒ `{P} if B then S1 else S2 {Q}`
* **Loop Rule**
  利用不變式(Invariant)I 來驗證迴圈正確性
### Assignment Rule(最重要的公理)
對於賦值語句:
```
x := E
```
若要證明 `{P} x := E {Q}` 成立,
你可以在 **Q 裡面把 x 換成 E**:
```
{Q[x ← E]}  x := E  {Q}
```
#### 範例:
```
x := y + 1
Postcondition: x > 5
```
在 Q 中把 `x` 換成 `y + 1`:
```
{y + 1 > 5}  x := y + 1  {x > 5}
```
所以執行前只要滿足 `y > 4`,執行後就會滿足 `x > 5`。
### Sequence Rule(序列規則)
兩段連續程式:
```
{P1} S1 {P2}
{P2} S2 {P3}
——————————————
{P1} S1; S2 {P3}
```
意思是:
> 若第一段保證 P2,第二段保證 P3,則整體保證 P3。
#### 範例:
```
{true} x := 1 {x = 1}
{x = 1} y := x + 2 {y = 3}
———————————————
{true} x := 1; y := x + 2 {y = 3}
```
### Selection Rule(if-else 規則)
```
{B ∧ P} S1 {Q}
{¬B ∧ P} S2 {Q}
——————————————
{P} if B then S1 else S2 {Q}
```
意思是:
> 若條件 B 為真時執行 S1 能得到 Q,
> 且條件 B 為假時執行 S2 也能得到 Q,
> 則整個 if-else 敘述都能保證 Q。
#### 範例:
```
{true} 
if (x > 0) then y := 1 else y := -1 
{(x > 0 → y = 1) ∧ (x ≤ 0 → y = -1)}
```
### Loop Rule(while 規則)
這是最難的部分,但你可以這樣理解:
```
{P} while B do S end {Q}
```
要證明一個迴圈正確,需要找出一個「**不變條件** (Loop Invariant, I)」。
Loop Invariant 是指「無論迴圈跑幾次,這條件永遠都成立」。
#### Loop Invariant 的四個條件:
1. **初始化時成立**(P ⇒ I)
2. **每次執行迴圈主體後仍成立**({I ∧ B} S {I})
3. **跳出迴圈時能推導出結果**(I ∧ ¬B ⇒ Q)
4. **迴圈一定會終止**(需有變數遞減或上升)
#### 範例(遞增計數):
```
x := 0;
while (x < 10) do
  x := x + 1;
end
```
想要證明 `{true} while (x < 10) do x := x + 1 {x >= 10}`
Loop Invariant 可選:`I: x <= 10`
| 條件      | 說明                                       |
| ------- | ---------------------------------------- |
| 初始時成立   | x=0 ⇒ x ≤ 10 ✅                           |
| 迴圈內仍成立  | 若 x ≤ 10 且 x < 10,執行 x := x+1 → x ≤ 10 ✅ |
| 結束時推出結果 | ¬(x < 10) ⇒ x ≥ 10 ✅                     |
| 會終止     | 每次 x 增加 1 ✅                              |
### 三種語意方式比較
| 方法               | 優點      | 缺點         |
| ---------------- | ------- | ---------- |
| **Operational**  | 直觀、易理解  | 太細節、依賴機器模型 |
| **Denotational** | 嚴謹、可數學化 | 太抽象、不易實作   |
| **Axiomatic**    | 可證明正確性  | 難以覆蓋所有語法   |
### Summary
* BNF 與 Context-Free Grammar:用於描述語法結構
* Attribute Grammar:補足語法結構的語意資訊
* 三大語意描述法:
  * **Operational**(以行為定義)
  * **Denotational**(以數學函數定義)
  * **Axiomatic**(以邏輯推論定義)
## Chapter 5: NAMES, BINDINGS, AND SCOPES
### Introduction
* 命令式語言是以 Von Neumann 架構為基礎的抽象
  * 包含記憶體與處理器兩部分
* 變數(Variable)是記憶體單元的抽象
* 設計變數與名稱時需考量:
  * scope(作用範圍)
  * lifetime(生命週期)
  * type checking(型別檢查)
  * initialization(初始化)
  * type compatibility(型別相容性)
### Names(名稱)
* 名稱(Name)是程式語言中用來指代變數、函數或物件的字串。
* 設計問題:
  * 是否區分大小寫(case sensitive)
  * 特殊字(special words)是否為保留字(reserved words)或關鍵字(keywords)
範例:
```cpp
int Value = 10;
int value = 20;  // 在 C++ 中不同變數,因為區分大小寫
```
```python
if = 5  # Python 錯誤,if 為保留字
```
### Name Length(名稱長度)
* 名稱太短會影響可讀性。
* 語言規定:
  * C99:無長度上限,但僅前 63 字有效;外部名稱最多 31 字元
  * C#、Java:無限制,全部有效
  * C++:無限制,但實作上通常有限制
* 範例:
  * userProfileSettingsForApplicationX()
  * userProfileSettingsForApplicationY()
### Special Characters(特殊字元)
* PHP:變數必須以 `$` 開頭,例如 `$username = "JohnDoe"`
* Perl:開頭符號代表變數類型
  * `$scalar`(純量)
  * `@array`(陣列)
  * `%hash`(雜湊)
* Ruby:
  * `@name` → instance variable
  * `@@classname` → class variable
### Case Sensitivity(大小寫敏感性)
* C、C++、Java、C# 為大小寫敏感語言。
* 缺點:
  * 可讀性下降,容易混淆(例如 totalAmount vs TotalAmount)
  * 在 C++、Java、C# 中更嚴重,因為內建名稱常為混合大小寫(如 IndexOutOfBoundsException)。
* 其他語言則不區分大小寫。
### Special Words(特殊字)
* 幫助可讀性,用於分隔程式結構。
* 區分兩種:
  * **Keyword(關鍵字)**:僅在特定情境下具特殊意義。
  * **Reserved word(保留字)**:不能作為自訂名稱使用。
* 保留字過多會造成名稱衝突,例如 COBOL 有超過 300 個保留字。
### Variables(變數)
* 變數是記憶體單元的抽象。
* 每個變數可用一組屬性描述:
  1. Name(名稱)
  2. Address(位址)
  3. Value(值)
  4. Type(型別)
  5. Lifetime(生命週期)
  6. Scope(作用範圍)
### Variable Attributes(變數屬性)
* **Name**:並非所有變數都有名稱(例如暫存器、匿名物件)。
* **Address**:變數所在的記憶體位置。
  * 不同時間、不同位置可能有不同位址。
  * 若兩個名稱指向同一個位址,稱為 **alias(別名)**。
  * Alias 常由 pointer、reference、或 union 造成,會降低程式可讀性。
### Type 與 Value
* **Type(型別)**
  * 決定變數可取的值範圍與可執行的運算。
  * 對浮點數而言,型別也決定精度。
* **Value(值)**
  * 記憶體位置內實際儲存的內容。
  * l-value(左值):位址
  * r-value(右值):內容
### Binding(繫結)
* Binding 是實體與屬性之間的關聯。
  例如:變數與型別、變數與值、符號與運算的關聯。
* Binding Time(繫結時間):繫結發生的時間點。
### Possible Binding Times(可能的繫結時間)
| 時間點   | 內容                      |
| ----- | ----------------------- |
| 語言設計時 | 將運算符號繫結到運算操作(如 `+` 表加法) |
| 語言實作時 | 定義資料型別如何表示(例如浮點數格式)     |
| 編譯期   | 將變數繫結到型別(例如 C、Java)     |
| 載入期   | 將靜態變數繫結到記憶體位置           |
| 執行期   | 將非靜態區域變數繫結到記憶體          |
### Static vs Dynamic Binding
* **Static Binding(靜態繫結)**
  * 繫結在執行前完成,執行期間不再改變。
  * 例:
    ```cpp
    void greet() { cout << "Hello!"; }
    int main() { greet(); } // greet 在編譯期就確定
    ```
* **Dynamic Binding(動態繫結)**
  * 執行時期才決定繫結。
  * 例:
    ```cpp
    class Base { virtual void show() { cout << "Base"; } };
    class Derived : public Base { void show() override { cout << "Derived"; } };
    int main() { Base* ptr = new Derived(); ptr->show(); } // 執行期才決定呼叫 Derived::show
    ```
### Type Binding(型別繫結)
* 兩個問題:
  * 型別如何指定?
  * 何時進行繫結?
若為靜態繫結,型別可透過下列方式決定:
* **Explicit Declaration(明確宣告)**
  * 由程式明確指出變數型別。
  * 例:`int x;`
* **Implicit Declaration(隱含宣告)**
  * 未明確宣告時,語言根據慣例自動推定型別。
  * 例:Python、Perl、JavaScript、Basic、Ruby、PHP
  
優缺點:
* 優點:可寫性高
* 缺點:可靠性下降(可能誤判型別)
### Type Inferencing(型別推斷)
* 編譯器根據變數使用情境自動判斷型別。
* C# 範例:
  ```csharp
  var number = 42;  // 編譯器自動推斷 number 是 int
  ```
* ML、Haskell、F# 也使用相同概念。
### Dynamic Type Binding(動態型別繫結)
* 在執行時期透過指定值決定型別。
* 常見於 Python、Ruby、PHP、JavaScript。
* 範例:
  ```js
  list = [2, 4.33, 6];
  list = 17.3;  // 型別於執行時改變
  ```
* 優點:彈性高
* 缺點:需動態型別檢查、執行成本高、編譯期無法偵錯。
### Storage Binding and Lifetime(儲存繫結與生命週期)
* **Allocation(配置)**:從可用記憶體池取得空間
* **Deallocation(釋放)**:將記憶體歸還
變數的 lifetime 是其與記憶體綁定的時間長度。
### Variable Lifetime 分類
1. **Static Variable(靜態)**
   * 於執行前繫結並在整個程式期間維持。
   * 優點:效率高(直接位址存取)、可保留歷史值。
   * 缺點:缺乏彈性、無法遞迴。
   * 例:
     ```cpp
     static int count = 0;
     ```
2. **Stack-dynamic Variable(堆疊動態)**
   * 宣告時才配置,離開區塊即釋放。
   * 優點:支援遞迴、節省記憶體。
   * 缺點:有配置與釋放開銷、不可保留歷史值。
   * C 的區域變數即屬此類。
3. **Explicit Heap-dynamic Variable(顯式堆積動態)**
   * 以程式語句明確分配與釋放。
   * 例:
     ```cpp
     int* ptr = new int(5);
     delete ptr;
     ```
   * 優點:支援動態記憶體管理
   * 缺點:效率低、容易出錯(如記憶體洩漏)
4. **Implicit Heap-dynamic Variable(隱含堆積動態)**
   * 由指派語句自動決定生命週期。
   * 範例(JavaScript):
     ```js
     let list = [1, 2, 3];
     list = "new string";  // 原記憶體自動釋放
     ```
   * 優點:彈性高
   * 缺點:效率低、容易出現隱藏錯誤
### Scope(作用範圍)
* 變數可見的程式區域稱為 scope。
* 區域變數:在程式單元內宣告。
* 非區域變數:在外層宣告但可被內層使用。
* 全域變數:可被所有單元存取。
scope 規則決定名稱參照到哪個變數。
### Static Scope(靜態作用範圍)
* 根據程式文本結構決定變數的可見性。
* 查找過程:
  1. 先從當前區域搜尋。
  2. 若找不到,向外層逐層尋找,直到全域區域。
外層稱為 **靜態祖先(static ancestor)**,最接近者為 **靜態父層(static parent)**。
範例(JavaScript):
```js
function outer() {
  var a = 10;
  function inner() {
    var b = 20;
    console.log(a);  // a 取自 outer()
  }
  inner();
}
outer();
```
### Scope Hiding(遮蔽)
* 內層定義相同名稱變數會遮蔽外層變數。
* 範例:
  ```cpp
  int x = 10;
  void func() {
    int x = 20;  // 遮蔽外層 x
    cout << x;   // 輸出 20
  }
  ```
### Block Scope(區塊範圍)
* ALGOL 60 引入區塊概念,可在區塊內重新定義名稱。
* C 與 C++ 支援此特性;Java 和 C# 禁止在同一方法內重複名稱。
* 範例:
  ```java
  public void method() {
    int count = 10;
    if (true) {
      int count = 20;  // 錯誤,Java 不允許同名變數
    }
  }
  ```
### The LET Construct(區域繫結結構)
* 在函數式語言中常見(如 Scheme、ML、F#)。
* 結構分兩部分:
  1. 第一部分:名稱與值的繫結。
  2. 第二部分:使用這些名稱。
Scheme 範例:
```scheme
(let ((x 5)
      (y 10))
  (+ x y))
```
ML 範例:
```ml
let val x = 5
    val y = 10
in
  x + y
end;
```
F# 範例:
```fsharp
let x = 5
let y = 10
x + y
```
### Declaration Order(宣告順序)
* C99、C++、Java、C# 允許在區塊中任意位置宣告變數。
* C99、C++、Java:變數作用範圍從宣告開始至區塊結尾。
* C#:整個區塊有效,但仍需先宣告再使用。
* C++、Java、C# 可在 for 迴圈中宣告:
  ```cpp
  for (int i = 0; i < 10; i++) {
    // i 僅在迴圈內有效
  }
  ```
### Global Scope(全域範圍)
* C、C++、PHP、Python 等允許在函式外宣告全域變數。
* C/C++:宣告(declaration)與定義(definition)區分。
  ```cpp
  // a.cpp
  extern int x;  // 宣告
  // b.cpp
  int x = 10;    // 定義
  ```
* PHP 全域變數可透過 `$GLOBALS` 陣列或 `global` 關鍵字使用。
  ```php
  $x = 10;
  function test() {
    global $x;
    $x = 20;
  }
  ```
* Python 全域變數需在函式中明確宣告:
  ```python
  x = 10
  def func():
      global x
      x = 20
  ```
### Evaluation of Static Scoping(靜態作用範圍的評價)
* 優點:可預測、容易理解
* 缺點:大型程式中易造成過度存取,結構混亂
### Dynamic Scope(動態作用範圍)
* 根據執行時的呼叫鏈決定變數可見性。
* 當前執行的所有活躍子程式的變數都可見。
範例:
```js
function big() {
  function sub1() { var x = 7; sub2(); }
  function sub2() { console.log(x); } // 動態範圍會取 sub1 的 x
  sub1();
}
big();
```
### Comparison of Static vs Dynamic Scope
| 類型     | 決定時機 | 查找方式  | 優點   | 缺點 |
| ----------- | ----------- | ----------- | ----------- | ----------- |
| 靜態作用範圍 | 編譯期  | 依文字結構 | 易於理解 | 彈性低 |
| 動態作用範圍 | 執行期  | 依呼叫順序 | 方便測試 | 難以追蹤、型別檢查困難 |
### Scope and Lifetime
* Scope 與 Lifetime 相關但不同。
* 例:
  ```cpp
  void func() {
    static int count = 0; // 生命週期長,作用範圍僅限此函式
    count++;
    cout << count;
  }
  ```
* 每次呼叫 count 都會累加,但外部無法存取。
### Referencing Environment(參照環境)
* 一個語句可見的所有名稱集合。
* 靜態語言中:由區域與外層可見變數組成。
* 動態語言中:由所有活躍的子程式變數組成。
範例(JavaScript):
```js
function outer() {
  var a = 10;
  function inner() {
    var b = 20;
    console.log(a + b);
  }
  inner();
}
```
參照環境為 {a, b}。
### Named Constants(具名常數)
* 常數在繫結到記憶體時即固定值。
* 優點:提升可讀性與可維護性。
* 可用於參數化程式。
* C++、Java:表達式可在執行期繫結。
* C#:有兩種常數:
  * const:編譯期繫結
  * readonly:執行期繫結
### Summary
* 名稱設計需考慮大小寫敏感性與特殊字。
* 變數具備六項屬性:名稱、位址、值、型別、生命週期、作用範圍。
* Binding 為實體與屬性間的繫結,依時間分靜態與動態。
* 變數依生命週期分為 static、stack-dynamic、explicit heap-dynamic、implicit heap-dynamic。
* Scope 決定名稱可見範圍,靜態與動態各有優缺。
* 強型別語言能偵測所有型別錯誤,提升可靠性。
## Chapter 6: DATA TYPES
### Introduction
* 資料型別(Data Type)定義了一組資料物件及可在其上執行的操作。
* **Descriptor(描述子)**:變數的屬性集合,例如型別、大小、範圍、位址。
* **Object(物件)**:資料型別的具體實例(實際存在於記憶體中的值)。
* 各種資料型別的設計問題:
  * 有哪些操作被允許?
  * 操作如何被定義與指定?
### Primitive Data Types(基本資料型別)
* 幾乎所有語言都提供一組基本資料型別。
* 基本型別:不由其他型別組成。
* 有些直接反映硬體設計,有些則需要軟體支援。
### Integer(整數)
* 幾乎完全對應硬體的整數格式。
* 語言中可能有多種不同大小的整數型別。
* 例:Java 提供四種帶號整數型別:
  * byte (8-bit)
  * short (16-bit)
  * int (32-bit)
  * long (64-bit)
### Floating Point(浮點數)
* 模擬實數,但僅為近似值。
* 用於科學運算語言中,通常提供至少兩種:
  * float(單精度)
  * double(雙精度)
* 通常採用硬體的浮點格式。
* IEEE 754 為常見的浮點數標準。
### Complex(複數)
* 某些語言支援複數型別,例如 C99、Fortran、Python。
* 每個值包含兩個浮點數:
  * 實部(real part)
  * 虛部(imaginary part)
* Python 寫法:
  ```python
  z = 7 + 3j
  ```
### Decimal(十進位數)
* 主要用於商業應用(例如金額計算)。
* COBOL 為代表性語言;C# 也支援 decimal 型別。
* 儲存固定小數位數(BCD 編碼)。
* 優點:精確
* 缺點:範圍有限、記憶體使用效率低。
### Boolean(布林值)
* 最簡單的資料型別。
* 僅有兩個值:true、false。
* 雖可用單一 bit 表示,但實作時常以 byte 儲存以提升可讀性。
### Character(字元)
* 字元以數字編碼方式儲存。
* 常見編碼:
  * ASCII(7-bit)
  * Unicode(16-bit, UCS-2)
  * Unicode(32-bit, UCS-4)
* Java、C#、JavaScript 採用 Unicode。
* Fortran 2003 開始支援 32-bit Unicode。
### Character String Types(字串型別)
* 字串是由字元組成的序列。
* 設計問題:
  * 字串是否為基本型別?或只是字元陣列的特殊形式?
  * 字串長度是固定還是可變?
### String Operations(字串操作)
常見操作包括:
* 指派與複製
* 比較(=、>、<)
* 串接(Concatenation)
* 子字串取用(Substring)
* 模式比對(Pattern Matching)
### String Types in Common Languages
* C / C++:非基本型別,以 char 陣列實作並透過函式庫操作。
* Fortran、Python:基本型別,支援直接操作。
* SNOBOL4:以字串操作為核心,支援進階模式比對。
* Java:透過 String 類別實現。
* Perl、JavaScript、Ruby、PHP:內建正規表示式比對功能。
### String Length Options(字串長度選擇)
* **Static(靜態長度)**:在宣告時固定(如 COBOL、Java String)。
* **Limited Dynamic(有限動態)**:有最大長度,實際長度可變(C, C++)。
* **Dynamic(完全動態)**:可隨程式執行變化(Perl、JavaScript)。
### String Implementation(字串實作)
* **Static**:於編譯期確定長度與位置。
* **Limited Dynamic**:執行期需要長度描述子。
* **Dynamic**:需動態配置與釋放記憶體,成本最高。
### Ordinal Types(序數型別)
* 可與正整數一一對應的型別。
* 範例:
  * 整數 (integer)
  * 字元 (char)
  * 布林 (boolean)
* 允許以順序或比較方式操作。
### Enumeration Types(列舉型別)
* 以名稱定義所有可能值。
* 範例:
  ```csharp
  enum days { mon, tue, wed, thu, fri, sat, sun };
  ```
* 設計考量:
  * 是否允許重複名稱?
  * 是否允許與整數自動轉換?
* 優點:
  * 提高可讀性(使用名稱代替數字)。
  * 提高可靠性(可檢查範圍與運算)。
* Java、C# 的列舉比 C++ 更安全,因為不會自動轉換為整數。
### Array Types(陣列型別)
* 陣列是**同質(homogeneous)** 的資料集合。
* 每個元素以索引(index)識別。
定義:
```
array_name(index_list) → element
```
### Array Design Issues
* 索引型別是否合法?
* 是否要檢查索引範圍?
* 索引範圍何時決定?
* 何時配置記憶體?
* 是否允許不規則(ragged)或矩形(rectangular)多維陣列?
* 陣列元素是否可初始化?
* 是否支援 slice(切片)操作?
### Array Indexing(索引)
* Fortran、Ada 使用括號 `()`
* C、Java 使用方括號 `[]`
* Ada 保持函式呼叫與索引一致(皆使用 `()`)
### Array Index Types(索引型別)
* Fortran、C:僅允許整數索引。
* Java:僅允許整數型別。
* 範圍檢查:
  * C、C++、Perl、Fortran:不強制檢查。
  * Java、C#、ML:強制檢查。
### Array Binding Categories(陣列繫結種類)
1. **Static**
   * 索引範圍與儲存於編譯時決定。
   * 優點:效率高(無需動態配置)。
2. **Fixed Stack-Dynamic**
   * 索引範圍固定,但配置於執行期宣告時完成。
3. **Fixed Heap-Dynamic**
   * 索引範圍固定,但記憶體從堆積分配。
4. **Heap-Dynamic**
   * 索引範圍與大小於執行期可變。
   * 優點:彈性高(可動態擴張)。
### Array Initialization(初始化)
* 可於宣告時初始化:
  ```cpp
  int list[] = {4, 5, 7, 83};
  char name[] = "freddie";
  ```
* Java:
  ```java
  String[] names = {"Bob", "Jake", "Joe"};
  ```
### Heterogeneous Arrays(異質陣列)
* 元素可為不同型別。
* 支援語言:Perl、Python、Ruby、JavaScript。
### Array Operations(陣列操作)
* APL 提供最強大的陣列運算(矩陣運算與反向操作等)。
* Python 支援串接、成員檢查與 list comprehension。
* Ruby 亦支援串接操作。
### Rectangular vs Jagged Arrays
* **Rectangular Array**:各行列元素數相同。
* **Jagged Array**:每列元素數不同(陣列中的陣列)。
* C、C++、Java 支援 jagged array。
* F#、C# 同時支援 rectangular 與 jagged。
### Array Slices(切片)
* Slice 是陣列的子結構參照。
* 只存在於支援陣列操作的語言中。
* 範例(Python):
  ```python
  vector = [2, 4, 6, 8, 10, 12]
  print(vector[2:5])  # [6, 8, 10]
  ```
### Array Implementation(陣列實作)
* 陣列存取由**存取函式(access function)** 計算實體位址。
* 一維陣列:
  ```
  address(list[k]) = address(list[lower_bound]) + ((k - lower_bound) * element_size)
  ```
* 多維陣列可使用 row-major(按列)或 column-major(按欄)方式配置。
### Associative Arrays(關聯陣列)
* 無序集合,以「鍵(key)」作為索引。
* 常見於腳本語言:Perl、Python、Ruby、Lua。
* Perl 範例:
  ```perl
  %hi_temps = ("Mon" => 77, "Tue" => 79, "Wed" => 65);
  $hi_temps{"Wed"} = 83;
  delete $hi_temps{"Tue"};
  ```
### Record Types(紀錄型別)
* 紀錄是**異質(heterogeneous)** 的資料集合。
* 每個欄位以名稱識別。
* 設計問題:
  * 欄位存取語法?
  * 是否允許省略上層名稱?
範例:
```cpp
struct Employee {
  string name;
  int age;
  double salary;
};
```
### Records in COBOL
* 使用層級號碼顯示巢狀結構:
  ```
  01 EMP-REC.
     02 EMP-NAME.
        05 FIRST PIC X(20).
        05 LAST  PIC X(20).
     02 HOURLY-RATE PIC 99V99.
  ```
### Record Field References
* COBOL:
  ```
  FIRST OF EMP-NAME OF EMP-REC
  ```
* 現代語言(C、Java):使用「.」運算子
  ```
  emp.name
  ```
* 可省略上層名稱(elliptical reference)只要不造成歧義。
### Record vs Array
* Record:用於異質資料集合,存取以名稱進行。
* Array:用於同質資料集合,存取以索引進行。
* 陣列存取較慢(索引動態);紀錄存取較快(欄位固定)。
### Tuple Types(元組型別)
* 類似紀錄,但元素**無名稱**。
* Python、ML、F# 均支援。
* Python:
  ```python
  myTuple = (3, 5.8, 'apple')
  a, b, c = myTuple
  ```
* 元組不可變(immutable)。
### List Types(列表型別)
* Lisp、Scheme:以括號表示:
  ```
  (A B C D)
  (A (B C) D)
  ```
* 代碼與資料使用相同表示方式。
* 基本操作:
  * CAR:回傳第一個元素
  * CDR:回傳其餘元素
  * CONS:將元素加入清單前端
  * LIST:建立新清單
ML 中:
```
[1, 3, 5]
3 :: [5, 7] => [3, 5, 7]
```
Python 中:
```
myList = [3, 5.8, "grape"]
del myList[1]
[x*x for x in range(6) if x%3==0]  # [0,9,36]
```
### Union Types(聯合型別)
* 同一變數可在不同時間儲存不同型別的值。
* 設計問題:是否應強制型別檢查?
* **Free Union(自由聯合)**:不檢查型別(C、C++)。
* **Discriminated Union(帶判別聯合)**:有型別指示器(ML、F#、Haskell)。
範例(F#):
```fsharp
type intReal =
  | IntValue of int
  | RealValue of float
```
### Pointer and Reference Types(指標與參照)
* 指標變數的值是記憶體位址或特殊值 nil。
* 用於間接取值或動態記憶體管理。
設計問題:
* 指標變數與目標的生命週期如何協調?
* 是否允許任意型別指標?
* 是否同時支援 reference?
### Pointer Operations
* **Assignment**:設定指標內容。
* **Dereferencing**:取指標指向位置的值。
* C++:
  ```cpp
  int x = 10;
  int* ptr = &x;
  cout << *ptr;  // 10
  ```
### Pointer Problems
1. **Dangling Pointer(懸掛指標)**:指向已釋放記憶體的指標。
2. **Lost Heap Variable(記憶體洩漏)**:動態分配後失去參照。
### Pointer Safety Techniques
* **Tombstone**:使用中介指標,指標失效後 tombstone 設為 nil。
* **Locks-and-Keys**:使用配對的 key 與 lock,確保指標有效。
### Heap Management(堆積管理)
* 回收未使用記憶體是執行期的重要工作。
* 方法:
  * **Reference Counting(參照計數)**:每個物件紀錄被參照次數,歸零時釋放。
    * 缺點:需額外空間與時間,遇到循環參照無法處理。
  * **Mark-Sweep(標記清除)**:定期標記可達物件並清除垃圾。
    * 缺點:早期版本會造成執行延遲,改進版採增量回收。
### Type Checking(型別檢查)
* 確保運算元與運算子型別相容。
* 若不相容但允許自動轉換,稱為 **coercion**。
* 錯誤運算稱為 **type error**。
靜態檢查:編譯時進行。
動態檢查:執行時進行。
### Strong Typing(強型別)
* 語言若能保證所有型別錯誤都被偵測,即為強型別語言。
* 例:
  * C、C++:非強型別(可略過檢查)
  * Java、C#:幾乎強型別
  * ML、F#:完全強型別
### Type Equivalence(型別等價)
* 判斷兩個變數的型別是否相同。
* 兩種方式:
  * **Name Equivalence(名稱等價)**:僅在使用相同名稱宣告時相等。
  * **Structure Equivalence(結構等價)**:型別結構完全相同即相等。
* Name 等價:容易實作但限制多。
* Structure 等價:彈性高但實作複雜。
### Theory and Data Types(理論基礎)
* 型別理論(Type Theory)研究資料型別的形式定義。
* 實務分支:商業語言中的資料型別。
* 抽象分支:以 lambda calculus 為基礎。
* 型別系統由:
  * 型別集合
  * 型別規則(由屬性文法或型別映射定義)
### Summary
* 資料型別決定了語言的風格與可用性。
* 基本型別:整數、浮點、布林、字元等。
* 使用者自訂型別:列舉、子範圍、陣列、紀錄、聯合、指標。
* 陣列與紀錄為主要的聚合資料型別。
* 指標提供彈性但帶來安全問題。
* 強型別語言能偵測所有型別錯誤,提升可靠性。
## Chapter 7: EXPRESSIONS AND ASSIGNMENT STATEMENTS
### Introduction
* **Expressions(運算式)** 是程式中用來描述計算的基本單位。
* 要了解運算式的行為,必須熟悉:
  * 運算子優先順序(operator precedence)
  * 運算子結合性(operator associativity)
  * 運算元求值順序(operand evaluation order)
* 命令式語言的核心是「**指派敘述(assignment statement)**」,用來改變變數值。
### Arithmetic Expressions(算術運算式)
* 由運算子(operators)、運算元(operands)、括號與函式呼叫所組成。
* 其設計重點包括:
  1. 運算子優先順序
  2. 運算子結合性
  3. 運算元求值順序
  4. 是否有副作用(side effects)
  5. 運算子多載(operator overloading)
  6. 型別混用(mixed-mode expressions)
### Operators(運算子種類)
* **一元運算子(unary)**:一個運算元
  例:`++x`、`x++`
* **二元運算子(binary)**:兩個運算元
  例:`a + b`、`a == b`
* **三元運算子(ternary)**:三個運算元
  例(C 語言):`(a > b) ? a : b`
### Operator Precedence Rules(運算子優先順序)
運算子優先順序決定當相鄰運算子不同時的求值順序。
常見順序:
1. 括號 `()`
2. 一元運算子 `++`, `--`, `-`
3. 乘除 `*`, `/`
4. 加減 `+`, `-`
例如:
```
int result = 5 + 3 * 2;  // 先算 3*2,再加 5,結果為 11
```
### Operator Associativity Rules(運算子結合性)
運算子結合性決定**相同優先等級**的運算子求值方向。
常見規則:
* 大部分為左結合(left-to-right)
* `**`(次方)通常為右結合(right-to-left)
* FORTRAN 的一元運算子採右結合
APL 語言特殊:所有運算子同等優先,皆為右結合。
括號可用來覆蓋優先順序與結合性。
### Expressions in Different Languages
* **Ruby**:所有運算子皆為方法,可被覆寫。
* **Scheme / Lisp**:所有運算皆為函式呼叫。
  例:`(+ a (* b c))` 等同於 `a + (b * c)`。
### Conditional Expressions(條件運算式)
* C、C++、Java 皆提供條件運算子 `?:`。
  ```c
  average = (count == 0) ? 0 : sum / count;
  ```
  等同於:
  ```c
  if (count == 0)
      average = 0;
  else
      average = sum / count;
  ```
### Operand Evaluation Order(運算元求值順序)
1. 變數:取記憶體中儲存的值。
2. 常數:直接使用常數或從指令中取值。
3. 括號表達式:先完全求值再回傳結果。
4. 函式呼叫:最複雜,因為可能有副作用。
### Functional Side Effects(副作用)
* 副作用是函式執行時改變了其他運算元或非區域變數的情況。
* 問題範例:
  ```c
  a = 10;
  b = a + fun(&a);  // fun() 若改變 a,結果取決於求值順序
  ```
### 處理副作用的兩種方式
1. **語言禁止副作用**
   * 不允許函式修改兩向參數或非區域變數。
   * 優點:安全、容易理解。
   * 缺點:限制彈性。
2. **固定運算元求值順序**
   * 例如 Java 要求「左至右」求值。
   * 缺點:減少編譯器最佳化空間。
### Referential Transparency(參照透明性)
* 若兩個相同值的運算式可在任何位置互換而不改變程式行為,則具**參照透明性**。
範例:
```
result1 = (fun(a) + b) / (fun(a) - c);
temp = fun(a);
result2 = (temp + b) / (temp - c);
```
若 `fun(a)` 無副作用,則 `result1 == result2`,否則不成立。
* 具參照透明性的語言(如純函數式語言)較容易分析與驗證正確性。
### Operator Overloading(運算子多載)
* 指相同的運算子可用於多種資料型別。
  例:`+` 可用於整數加法與字串串接。
* 優點:增加可讀性與自然性。
* 缺點:
  * 可能降低可讀性(不易分辨用途)。
  * 可能隱藏錯誤(少一個運算元時編譯器無法偵測)。
  
例(Python):
```python
3 + 5       # 數值加法
"Hi" + "!"  # 字串串接
```
C++、C#、F# 允許使用者自訂運算子多載。
### Type Conversions(型別轉換)
* **Narrowing conversion**:轉換成較小範圍型別(如 float → int)。
* **Widening conversion**:轉換成較大範圍型別(如 int → float)。
### Mixed-Mode Expressions(混合型運算式)
* 當運算元型別不同時會自動進行轉換。
* **Coercion(強制轉換)**:編譯器自動插入型別轉換程式碼。
* 缺點:降低型別檢查能力。
* 大部分語言允許數值型混合運算。
* ML、F# 等函數式語言不允許 coercion。
### Explicit Type Conversions(明確型別轉換)
* 程式設計師主動指定轉型。
* C 語言稱為「casting」:
  ```c
  (int) angle;
  ```
* F# 使用類似函式呼叫語法:
  ```fsharp
  float(sum)
  ```
### Errors in Expressions(運算式錯誤)
* 可能原因:
  * 除以零(division by zero)
  * 溢位(overflow)
* 許多執行環境不會自動攔截此類錯誤,需由語言或開發者自行處理。
### Relational and Boolean Expressions(關係與布林運算式)
* **Relational operators**:比較兩運算元,結果為布林值。
  例:`==`, `!=`, `<`, `<=`, `>`, `>=`
* 不同語言使用不同符號:
  * Pascal:`<>`
  * FORTRAN:`.NE.`
  * Ruby:`==`(含型別轉換)與 `eql?`(不含轉換)
* JavaScript、PHP 提供 `===` 與 `!==`,不進行型別轉換。
* **Boolean operators**:`&&`, `||`, `!`
注意:C 語言無布林型別,以 `0` 代表 false,非零代表 true。
### Short-Circuit Evaluation(短路求值)
* 若運算結果已可確定,則可跳過剩餘求值。
* 範例:
  ```c
  if (x != 0 && y / x > 2)
  ```
  若 `x == 0`,第二個條件不會被執行。
* 範例中若未採短路求值,可能造成除以零錯誤。
### Short-Circuit Evaluation in Languages
* C、C++、Java:`&&`、`||` 採短路求值。
* 但 `&`、`|` 為位元運算,不短路。
* Python、Ruby、Perl、ML、F# 全部邏輯運算皆採短路求值。
* 注意:短路求值可能影響副作用的發生順序。
  例:
  ```c
  (a > b) || (b++ / 3)
  ```
### Assignment Statements(指派敘述)
* 一般語法:
  ```
  <target_var> <assign_operator> <expression>
  ```
* 常見形式:
  * `=`  (Fortran、C、Java)
  * `:=` (Ada、Pascal)
C 語言區分等號與相等:
```
=   → 指派
==  → 比較
```
### Conditional Targets(條件目標)
* Perl 特有的條件式指派:
  ```perl
  ($flag ? $total : $subtotal) = 0;
  ```
  等價於:
  ```perl
  if ($flag) { $total = 0; } else { $subtotal = 0; }
  ```
### Compound Assignment Operators(複合指派運算子)
* 簡化常見運算:
  ```
  a = a + b;
  ```
  可寫成:
  ```
  a += b;
  ```
* ALGOL 最早引入,C 系語言全面採用。
### Unary Assignment Operators(一元指派運算子)
* C 系語言支援自增與自減運算:
  ```
  sum = ++count; // count 先加一再指派給 sum
  sum = count++; // 先指派再加一
  ```
* 也可單獨使用:
  ```
  count++;
  -count++;
  ```
### Assignment as an Expression(指派運算式)
* 在 C、Perl、JavaScript 中,指派語句可作為運算式使用。
  例:
  ```c
  while ((ch = getchar()) != EOF) { ... }
  ```
  * `ch = getchar()` 的結果同時被指派與測試。
* 缺點:可能導致難以閱讀與副作用。
### Multiple Assignments(多重指派)
* Perl、Ruby、Lua 支援一次指派多個變數:
  ```perl
  ($first, $second, $third) = (20, 30, 40);
  ($first, $second) = ($second, $first);  // 交換值
  ```
### Assignment in Functional Languages(函數式語言中的指派)
* 在純函數式語言中,名稱僅代表值,不可改變。
* ML 使用 `val` 繫結:
  ```
  val fruit = apples + oranges;
  ```
* F# 使用 `let`:
  ```
  let fruit = apples + oranges
  ```
每次新的 `val` 或 `let` 會建立新的繫結,而非修改原值。
### Mixed-Mode Assignment(混合型指派)
* 不同型別間的指派。
* Fortran、C、Perl、C++:允許任意數值型轉換。
* Java、C#:僅允許「擴大轉換(widening)」。
* Ada:完全禁止自動轉換。
### Summary
* 運算式描述程式中的運算行為。
* 主要設計議題:
  * 優先順序與結合性
  * 副作用與短路求值
  * 型別轉換與多載
* 指派語句是命令式語言的核心,負責更新記憶體中的變數值。
* 函數式語言則以不可變(immutable)值與繫結取代指派概念。