內容已複查
33 分鐘 · 6 張概念卡 · 6 題對應考古題

程式執行基礎:型別、控制流程、函式與記憶體

本頁為依教材與考古題整理的原創摘要;考古題答案經技術覆核,但不是官方答案。

考古題證據邊界: 指標與動態記憶體在 canonical metadata 為 0 題 direct primary refs;本節是 foundational coverage,引用的 refs 直接支撐型別、程式典範、執行特性與遞迴,不把它們當成指標題的直接證據。

第一次接觸也沒關係

這堂先懂這些詞

先記住白話意思,不必急著背英文。看到正文時,再把正式名稱接回來。

作用域、遞迴與參照

也會看到:lexical scope、recursion、base case、pointer、reference

Scope 決定名稱在哪裡可見;recursion 讓函式呼叫自己並靠 base case 停止;pointer/reference 指向其他資料。

生活例子:
像在一層層盒子裡找東西,每層記住返回位置,找到最小盒便停止往下。
別搞混:
遞迴若沒有可達的 base case 會一直呼叫;指標存在也不代表目標物仍有效。

堆疊與堆積記憶體

也會看到:stack、heap、call stack、堆疊、堆積

stack 常隨函式呼叫自動進出,heap 則放生命週期較彈性的動態資料。

生活例子:
stack 像依序疊盤子,heap 像倉庫中另行申請與歸還空間。
別搞混:
這裡的 heap 是記憶體區域,不是資料結構中的 heap。

型別與控制流程

也會看到:type system、control flow、if、loop、switch

Type system 限制資料能做哪些操作;control flow 決定程式下一步執行哪一段。

生活例子:
像表單欄位限制日期不能填姓名,而路口號誌決定車流下一步往哪走。
別搞混:
型別描述資料與操作規則,控制流程描述執行順序,兩者不是同一件事。

從 type system 與 control flow 追蹤程式狀態,再用 function call、scope、recursion、stack、heap 與 pointer/reference 說明資料的可見範圍與生命週期。

先抓住這幾件事

  • 以型別與 control flow 追蹤程式
  • 區分 lexical scope 與 object lifetime
  • 識別 recursion 的 base case 與縮小問題
  • 說明 pointer/reference、stack、heap 與 leak/dangling 風險

先想像這個場景

圖書館借閱系統

借閱系統對每筆資料規定型別,依條件決定能否借書,把重複步驟封裝成函式,並以索引史記錄實際書籍的存放位置。

先別急著往下看,花十秒想一想:

若索引卡還在,但它指向的書已被移除,再依索引取書會有什麼風險?

把故事換成電腦語言

生活中的角色對應到技術概念
借閱人數是整數,書名是字串Type 與 type checking
有欠款就拒絕,否則進入借閱流程Conditional control flow
借閱步驟封裝成可重複的服務Function、parameters 與 return value
處理子分類時反覆套用相同步驟,到空分類停止Recursion 與 base case
索引卡保存書籍位置,不是書本身Pointer/reference 與 referenced object

題目出現這些字,先想到

  • Static/dynamic typing 問的是型別檢查時機,不是有沒有 types。
  • Scope 是名稱在哪裡可見;lifetime 是 object 存在多久。
  • Recursion 必須有 base case 與向其靠近的小問題。
  • Dangling pointer 指向已失效物件;memory leak 是不再需要卻無法回收。

1.型別與控制流程

程式語言透過 data type(資料型別)告訴電腦如何解讀記憶體中的 bit pattern。常見的 primitive types: • int(整數):通常 32 bits,範圍 -2³¹ 到 2³¹-1(使用 2's complement)。 • float/double(浮點數):遵循 IEEE 754 標準。float 32 bits(~7 位有效數字),double 64 bits(~15 位有效數字)。浮點數有精度限制 — 0.1 + 0.2 ≠ 0.3(因為 0.1 在二進位中是無限循環小數)。 • char(字元):通常 1-2 bytes。ASCII 使用 7 bits(128 字元),Unicode/UTF-8 可變長度。 • boolean:true 或 false。 【型別的重要性】同樣的 bit pattern 01000001,作為 int 解讀是 65,作為 char 解讀是 'A',作為 float 解讀是另一個數。型別決定了解讀方式、運算方式和佔用空間。 【控制流程】程式不一定從第一行執行到最後一行。控制流程結構讓程式能做選擇和重複: • Sequential(循序):按順序一行一行執行。 • Selection(選擇/條件):if-else, switch-case。根據條件決定執行哪段程式碼。 if (score >= 60) pass(); else fail(); • Iteration(迴圈/重複):for, while, do-while。重複執行一段程式碼直到條件不成立。 for (int i = 0; i < n; i++) { ... } // 執行 n 次 while (x > 0) { x = x / 2; } // 直到 x ≤ 0 【Short-circuit evaluation】邏輯運算的短路求值: • A && B:如果 A 是 false,不評估 B(因為結果一定是 false)。 • A || B:如果 A 是 true,不評估 B(因為結果一定是 true)。 【考試連結】(1) 浮點數的精度問題(0.1+0.2≠0.3)。(2) for 迴圈的初始化、條件、更新三個部分。(3) 追蹤程式碼的執行流程是常見的手動追蹤題。(4) int overflow:32-bit int 最大值 2³¹-1 = 2147483647,加 1 會 overflow。

  • Dynamic typing 仍有 runtime types
  • Cast 不保證資料不損失
  • Loop 要檢查初始、條件、更新

2.函式、scope 與 lifetime

Function(函式/方法)是把一段可重複使用的程式碼封裝成一個有名字的區塊,接受參數、執行運算、回傳結果。 【函式的基本結構】 int add(int a, int b) { // return type, function name, parameters int result = a + b; // function body return result; // return statement } int sum = add(3, 5); // function call, arguments • Parameter(參數/形式參數):函式定義中的變數名(a, b)。 • Argument(引數/實際參數):呼叫函式時傳入的值(3, 5)。 【Pass by Value vs Pass by Reference】 • Pass by value:傳入值的副本。函式內修改 parameter 不影響外部的原始變數。Java 的 primitive types 和 C 預設都是 pass by value。 • Pass by reference:傳入變數的「參考/位址」。函式內的修改直接影響外部的原始變數。C++ 使用 & 符號,Java 的 object 傳的是 reference 的值(pass reference by value)。 【Scope(作用域)】變數在程式中可以被存取的範圍。 • Global scope:在所有函式外宣告,整個程式都能存取。應盡量避免(降低耦合)。 • Local scope:在函式或 block 內宣告,只在該範圍內可見。 • Block scope:在 { } 內宣告的變數只在該 block 內可見(如 for 迴圈的 int i)。 【Lifetime(生命週期)】變數在記憶體中存在的時間。 • Automatic(自動):local variable,函式執行時建立、結束時銷毀。存在 stack 上。 • Static:static variable,程式執行期間一直存在。初始化只執行一次。 • Dynamic:用 malloc/new 手動配置,用 free/delete 手動釋放。存在 heap 上。 【考試連結】(1) Pass by value 和 pass by reference 的行為差異是常考追蹤題。(2) Local variable 的 scope 和 lifetime。(3) Global variable 的風險和替代方案。

  • Parameter passing 規則依語言而定
  • Local variable 通常只在 block/function 內可見

3.遞迴與 call stack

Recursion(遞迴)是函式呼叫自己的技巧。每次遞迴呼叫都會在 call stack 上新增一個 stack frame,包含該次呼叫的 local variables、parameters 和 return address。 【遞迴的兩個必要條件】 (1) Base case(基礎案例):不再遞迴的終止條件。沒有 base case → 無限遞迴 → stack overflow。 (2) Recursive case(遞迴案例):把問題分解成更小的子問題,呼叫自己解決。每次遞迴呼叫必須朝 base case 靠近。 【經典範例:階乘 n!】 int factorial(int n) { if (n <= 1) return 1; // base case return n * factorial(n - 1); // recursive case } factorial(4) 的執行過程: factorial(4) → 4 * factorial(3) factorial(3) → 3 * factorial(2) factorial(2) → 2 * factorial(1) factorial(1) → 1 (base case, 開始回傳) → 2 * 1 = 2 → 3 * 2 = 6 → 4 * 6 = 24 【Call Stack 運作】每次函式呼叫(不只是遞迴)都在 stack 上 push 一個 frame。Frame 包含:parameters、local variables、return address(呼叫完成後要回到哪裡繼續執行)。函式 return 時 pop 該 frame,根據 return address 回到呼叫者。 【Stack Overflow】遞迴太深(例如 n 太大,或忘記 base case),stack frames 把 stack 的空間用完 → stack overflow error。解法:(1) 確保有正確的 base case。(2) 改用 iterative(迴圈)版本。(3) Tail recursion optimization(尾遞迴優化,部分語言和編譯器支援)。 【遞迴 vs 迴圈】任何遞迴都可以改寫成迴圈(使用顯式 stack)。遞迴通常程式碼更簡潔(特別是 tree traversal、divide-and-conquer),但空間效率較差(每次呼叫都用 stack space)。 【考試連結】(1) 手動追蹤遞迴的執行過程是高頻考題。(2) 遞迴的時間複雜度分析(如 fibonacci 的 O(2ⁿ))。(3) Stack frame 包含什麼。(4) Stack overflow 的原因和解法。

  • 先找 base case
  • 再驗證每步使問題變小

4.指標與動態記憶體

Pointer(指標)是一個變數,其值是另一個變數的記憶體位址。指標讓程式能直接操作記憶體位址,是 C/C++ 的核心概念。 【基本語法(C 語言)】 int x = 42; // x 是 int,值為 42 int *p = &x; // p 是 int 指標,存放 x 的位址。& 是取址運算子。 printf("%d", *p); // *p 是解引用(dereference),取得 p 指向的值 → 42 *p = 100; // 透過指標修改 x 的值 → x 變成 100 【指標與陣列】在 C 中,陣列名是指向陣列第一個元素的指標。a[i] 等價於 *(a+i)。指標算術:p+1 不是加 1 byte,而是加一個元素的大小(如 int* 加 4 bytes)。 【動態記憶體配置】 程式的記憶體分為: • Stack:local variables、function parameters。自動管理(function return 就釋放)。空間較小。 • Heap:動態配置的記憶體。手動管理(程式設計師負責配置和釋放)。空間較大。 C 語言: int *arr = (int*)malloc(10 * sizeof(int)); // 在 heap 上配置 10 個 int // 使用 arr... free(arr); // 手動釋放,否則 memory leak C++: int *arr = new int[10]; // 配置 delete[] arr; // 釋放 【Memory Leak(記憶體洩漏)】配置了記憶體但忘記釋放,程式佔用的記憶體不斷增加。長時間運行的程式(如 server)如果有 memory leak,最終可能耗盡記憶體。 【Dangling Pointer】指標指向的記憶體已經被釋放。存取 dangling pointer 是 undefined behavior — 可能 crash、可能讀到垃圾值、可能看起來正常但在別處出錯。 【Garbage Collection(GC)】Java、Python 等語言使用 GC 自動追蹤哪些物件不再被引用,自動回收記憶體。程式設計師不需要手動 free。優點是避免 memory leak 和 dangling pointer;缺點是 GC 執行時可能造成暫停(pause)。 【考試連結】(1) 指標的基本操作(取址 &、解引用 *)。(2) Stack 和 heap 的差異。(3) Memory leak 和 dangling pointer 的定義和原因。(4) GC 和手動記憶體管理的比較。

  • Null 不指向有效 object
  • Use-after-free 與 leak 是不同錯誤
  • Ownership 可幫助明確誰負責釋放

一起拆題目

範例 1遞迴 factorial(4) 如何展開與收斂?

  1. factorial(1)=1 是 base case。
  2. factorial(4)=4×factorial(3)。
  3. 逐步到 1 後回傳相乘。

所以答案是:4×3×2×1=24;每步參數減 1 保證收斂。

範例 2程式釋放 object 後仍保留 raw pointer,有何問題?

  1. Pointer 值仍是舊位址。
  2. Object lifetime 已結束。
  3. 再 dereference 屬 invalid access。

所以答案是:形成 dangling pointer;應依語言使用 ownership、smart pointer 或安全生命週期機制。

這裡最容易選錯

  • 說 dynamically typed 語言沒有 types
  • 把 scope 與 lifetime 當同一件事
  • 遞迴少了 base case
  • 把 pointer 本身與它指向的 object 混淆
  • 把 leak 與 dangling pointer 當同種錯誤

換你快速判斷

先在心中作答,再展開答案。答不出來時,回頭找本課的對照關係。

1Type system 在程式語言中的核心用途是什麼?

替 values、variables、expressions 與 functions 指派或檢查型別規則,限制不合法操作並提早暴露錯誤。

型別檢查發生時機依語言而異;本卡只陳述 type system 的共同目的。

2追蹤 loop 時最少要檢查哪三件事?

初始狀態、繼續/停止條件、每輪狀態更新。

建立逐輪表格可避免 off-by-one 誤判。

3Scope 與 lifetime 有何不同?

Scope 是名稱可見的程式區域;lifetime 是 object 在執行期存在的時間。

Object 可在建立它的 local name 離開 scope 後仍被其他 reference 保留。

4正確 recursion 的兩個必要條件?

要有 base case,且 recursive step 要使問題逐步靠近 base case。

否則可能無限呼叫並造成 stack overflow。

5Dangling pointer 與 memory leak 的差異?

Dangling pointer 指向已失效 object;leak 是不再需要的配置無法被回收。

前者可導致 invalid access,後者逐步消耗記憶體。

6Stack 與 heap 的典型角色?

Stack 常管理 calls 的 activation records;heap 常供生命週期不緊綁單一 call 的動態 objects。

這是概念模型,精確配置取決於 compiler/runtime。

最後用考古題驗證

本課連結的題目都已通過可重現的技術覆核,可逐題練習與判分。

開始本課考古題練習

參考來源