程 式 是 什 麼 ? 最 粗 略 的 分 法 , 程 式 是 由 「 程 式 碼 」 及
「 資 料 」 所 組 成 的 。 就 程 式 碼 來 說 , 它 可 能 包 含 陳 述 式
、 指 令 、 控 制 流 程 的 述 句 、 副 程 式 及 主 程 式 等 , 至 於 資
料 部 份 , 在 程 式 語 言 裡 面 則 是 以 變 數 來 展 現 的 。
初 學 程 式 設 計 通 常 會 把 重 點 放 在 如 何 把 程 式 碼 組 織 起 來 上 面 , 也 就 是 編 碼 的 動 作 , 但 是 對 於 一 個 已 經 編 好 碼 的 程 式 來 說 , 能 不 能 跑 出 正 確 的 結 果 卻 與 變 數 有 極 大 的 關 係 , 簡 單 的 例 子 如 : If A > 0 Then Call DO_THING_1 Else Call DO_THING_2 EndIf 變 數 A 的 值 將 決 定 程 式 會 執 行 到 DO_THING_1 或 DO_THING_2; 而 更 複 雜 的 變 數 , 如 「 資 料 結 構 」 中 的 陣 列 、 堆 疊 、 佇 列 … 等 還 衍 生 出 一 門 專 業 的 學 科 , 所 以 說 充 分 掌 握 變 數 才 能 掌 握 程 式 執 行 的 結 果 。 從 以 上 的 討 論 , 我 們 可 以 說 程 式 是 由 控 制 流 程 的 「 程 式 碼 」 及 影 響 執 行 結 果 的 「 變 數 」 所 組 成 。 如 何 控 制 程 式 的 流 程 , 相 信 您 在 學 習 第 一 個 程 式 語 言 時 , 已 經 領 略 其 精 髓 , 這 個 部 份 在 不 同 程 式 語 言 之 間 並 不 會 有 太 大 的 差 異 , 我 們 在 此 將 不 再 討 論 , 因 此 本 文 要 把 重 點 放 在 變 數 的 各 種 變 化 上 面 , 有 : • 變 數 的 組 成 元 素 : 從 變 數 的 基 本 原 理 中 探 討 各 種 程 式 語 言 在 變 數 使 用 上 的 差 異 及 特 殊 之 處 。 • 副 程 式 之 間 的 參 數 傳 遞 方 式 : 比 較 傳 值 呼 叫 及 傳 位 址 呼 叫 的 差 異 , 以 及 幾 個 常 見 的 程 式 語 言 ( 如 C/C++、 PASCAL、 BASIC 等 ) 如 何 決 定 其 參 數 的 傳 遞 方 式 。 • 變 數 的 活 動 範 圍 及 生 命 週 期 : 比 較 全 域 變 數 、 區 域 變 數 、 及 靜 態 變 數 的 差 異 , 以 及 如 何 正 確 地 使 用 每 一 種 變 數 。 |
什 麼 是 變 數 ? 我 們 寫 一 個 數 學 方 程 式 , 假 設 是 :
X = Y + 10 這 裡 面 就 有 兩 個 變 數 , 一 個 是 X, 一 個 是 Y。 X 及 Y 在 這 裡 是 變 數 的 「 名 稱 」 ; 而 當 Y 等 於 2 時 , X 等 於 12, 此 時 2 及 12 分 別 是 變 數 X 與 Y 的 「 值 」 。 以 上 是 我 們 從 數 學 方 程 式 中 所 看 到 的 變 數 , 它 包 含 兩 個 元 素 : 名 稱 與 值 。 若 從 電 腦 的 角 度 來 看 , X 的 值 12 必 須 儲 存 在 某 一 個 地 方 , 也 就 是 記 憶 體 的 某 一 個 「 位 址 」 之 下 , 所 以 位 址 又 是 變 數 的 另 一 個 元 素 。 為 了 表 達 一 個 變 數 , 只 有 以 上 三 個 元 素 往 往 還 是 不 夠 的 , 舉 例 來 說 : A = "RUN!程 式 設 計 " 這 時 候 儲 存 變 數 A( 等 於 "RUN!程 式 設 計 ") 所 需 的 空 間 , 顯 然 比 儲 存 變 數 X ( 等 於 12) 所 需 的 空 間 來 得 大 , 而 且 連 操 作 兩 種 變 數 的 方 式 也 會 有 所 差 異 , 例 如 我 們 可 以 這 樣 寫 X/2( 表 示 X 除 以 2) , 但 如 果 我 們 這 樣 寫 A/2 就 沒 有 意 義 了 , 為 了 區 別 特 性 不 同 的 變 數 , 程 式 語 言 中 的 變 數 必 須 再 增 加 一 個 元 素 「 資 料 型 態 」 , 以 前 面 的 例 子 來 看 , 變 數 X 是 整 數 型 態 , 而 變 數 A 是 字 串 型 態 。 綜 合 以 上 的 討 論 , 我 們 可 以 用 以 下 四 個 元 素 : 名 稱 、 資 料 型 態 、 位 址 、 及 值 來 表 示 一 個 變 數 : 圖-1 變 數 的 四 個 組 成 元 素 在 上 圖 中 , 由 於 「 值 」 是 儲 存 在 某 一 個 「 位 址 」 的 記 憶 體 裡 面 的 , 所 以 把 它 畫 在 位 址 的 框 框 裡 面 。 利 用 以 上 四 個 元 素 來 表 示 變 數 , 可 以 解 釋 一 些 平 常 想 當 然 而 的 式 子 , 例 如 : 「 X = Y + 10」 , 假 設 Y 的 值 等 於 123: 圖-2 式 子 「 X = Y + 10」 的 運 算 過 程 這 裡 順 便 介 紹 兩 個 術 語 : r-value 及 l-value。 r-value 是 right value 的 縮 寫 , 表 示 等 號 右 邊 的 變 數 , 如 例 子 中 的 Y, 實 質 上 它 代 表 的 是 變 數 的 「 值 」 , l-value 是 left value 的 縮 寫 , 表 示 等 號 左 邊 的 變 數 , 如 例 子 中 的 X , 實 質 上 它 代 表 的 是 變 數 的 「 位 址 」 。 以 上 我 們 用 變 數 的 組 成 元 素 來 解 釋 簡 單 的 例 子 , 看 起 來 沒 有 特 別 的 價 值 , 但 這 是 變 數 的 根 本 道 理 , 它 同 時 可 以 用 來 解 說 複 雜 、 以 及 容 易 讓 人 搞 錯 例 子 , 例 如 C 語 言 中 的 指 標 變 數 , 請 務 必 瞭 解 這 幾 個 元 素 的 意 義 。 一 般 來 說 , 變 數 都 含 有 以 上 四 個 元 素 , 但 是 在 程 式 語 言 的 演 變 中 , 並 不 是 所 有 的 變 數 都 可 以 明 確 地 看 到 這 幾 個 元 素 , 以 下 是 針 對 大 部 份 的 程 式 語 言 所 整 理 出 來 的 變 數 類 型 , 茲 說 明 如 下 : • 定 型 變 數 • 多 型 變 數 • 同 址 變 數 • 指 標 變 數 |
此 一 類 型 的 變 數 在 宣 告 時 會 指 明 變 數 的 「 資 料 型 態 」
, 例 如 將 變 數 X 宣 告 成 整 數 型 態 , 大 部 份 的 程 式 語 言 都
有 此 一 類 型 的 變 數 , 以 下 是 幾 個 例 子 :
圖-3 定型變數 請 注 意 圖 中 我 們 把 X 的 值 標 成 ? , 千 萬 不 要 假 設 它 等 於 0, 以 前 面 所 列 舉 的 程 式 語 言 來 說 , QBASIC 及 Visual Basic 會 將 變 數 初 設 為 零 , 但 是 C 語 言 卻 不 會 這 麼 做 。 此 一 類 型 變 數 的 最 大 特 色 在 於 其 資 料 型 態 是 「 固 定 的 」 , 也 就 是 說 , 當 我 們 宣 告 變 數 時 , 指 定 給 它 某 一 型 態 後 , 它 就 永 遠 是 那 個 型 態 , 而 且 將 來 就 只 能 與 型 態 「 相 同 」 或 「 相 容 」 的 變 數 做 運 算 , 舉 例 來 說 , 以 下 的 例 子 會 出 現 編 譯 錯 誤 : Dim X as Integer X = "這是字串 " ' 編 譯 時 會 出 現 「 型 態 不 符 合 」 的 訊 息 請 注 意 一 件 事 情 , 型 態 「 相 同 」 的 變 數 彼 此 可 以 互 相 運 算 , 對 任 何 程 式 語 言 來 說 都 沒 有 例 外 的 , 但 是 型 態 「 相 容 」 的 變 數 ( 例 如 : 整 數 與 實 數 , 長 度 不 等 的 整 數 , 如 2-bytes 的 整 數 與 4-bytes 的 整 數 ) 彼 此 之 間 是 否 可 以 互 相 運 算 , 在 程 式 語 言 中 卻 可 分 成 兩 大 流 派 。 絕 對 禁 止 「 不 同 」 ( 相 容 當 然 視 為 不 同 ) 型 態 的 變 數 互 相 運 算 的 程 式 語 言 有 PASCAL、 ADA 等 , 為 什 麼 要 禁 止 呢 ? 不 妨 讓 我 們 直 接 來 看 「 相 容 」 派 可 能 衍 生 的 問 題 。 允 許 相 容 型 態 的 變 數 互 相 運 算 的 程 式 語 言 有 C 語 言 、 BASIC 系 的 程 式 語 言 等 , 例 如 以 下 的 式 子 在 C 語 言 及 QBASIC 中 分 別 是 合 法 的 : C : int i; // 整 數 型 態 float r; // 實 數 型 態 i = r; // 相 容 型 態 變 數 的 指 定 QBASIC: Dim i As Integer ' 整 數 型 態 Dim r As Single ' 實 數 型 態 i = r ' 相 容 型 態 變 數 的 指 定 這 樣 做 在 程 式 設 計 時 比 較 有 彈 性 , 但 使 用 時 要 特 別 留 意 編 譯 器 ( 或 解 譯 器 ) 是 如 何 處 理 「 型 態 轉 換 」 的 , 例 如 QBASIC 例 子 的 Dim i as Integer i = 123.5 Print i 其 輸 出 結 果 等 於 124, 但 類 似 的 例 子 在 C 語 言 中 : int i; i = 123.5; printf("%d", i ); 其 輸 出 結 果 卻 等 於 123。 下 面 的 C 語 言 例 子 也 是 因 為 型 態 轉 換 所 出 現 的 陷 阱 : char byte; byte = 156; if ( byte >= 128 ) { printf("byte 大 於 128"); } // 執 行 後 , 卻 發 現 printf() 不 會 被 執 行 到 本 例 的 byte 變 數 宣 告 成 char 的 資 料 型 態 , 在 C 語 言 中 char 被 視 為 有 正 負 值 的 1 byte 資 料 , 因 此 數 值 的 範 圍 是 -128 到 127, 這 使 得 變 數 byte 在 執 行 「 byte = 156;」 時 產 生 溢 位 的 現 象 , 而 變 成 負 值 , 所 以 if ( byte >= 128 ) 也 就 不 能 如 我 們 所 預 期 的 得 到 TRUE。 當 然 這 並 不 表 示 相 容 派 的 程 式 語 言 比 較 不 好 , 只 是 您 在 享 受 它 的 彈 性 時 , 也 要 特 別 注 意 它 潛 在 的 陷 阱 。 |
什 麼 是 多 型 變 數 ? 請 看 以 下 Visual Basic 的 例 子 :
Dim X ' 宣 告 一 個 變 數 , 但 未 指 定 資 料 型 態 X = 123 ' 先 是 「 整 數 」 型 態 ... X = "Run!程 式 設 計 " ' 後 來 又 變 成 「 字 串 」 型 態 圖 -4 多 型 變 數 在 不 同 時 間 被 指 定 成 不 同 的 資 料 型 態 變 數 X 在 某 一 個 時 間 被 指 定 成 「 整 數 」 的 資 料 型 態 , 但 是 後 來 它 又 被 指 定 另 一 種 資 料 型 態 ─ 「 字 串 」 , 像 X 一 樣 其 資 料 型 態 可 以 變 來 變 去 的 變 數 就 是 多 型 變 數 。 並 不 是 每 一 種 程 式 語 言 都 提 供 多 型 變 數 的 功 能 , 我 們 常 聽 到 的 程 式 語 言 不 具 有 此 類 型 變 數 的 有 C/C++、 PASCAL、 ADA 等 , 具 有 此 類 型 變 數 的 有 Clipper 、 SmallTalk、 Visual Basic、 Visual Objects 等 。 多 型 變 數 最 大 的 缺 點 是 執 行 效 率 比 較 差 , 為 什 麼 呢 ? 由 於 它 的 資 料 型 態 是 可 變 動 的 , 因 此 每 次 執 行 時 必 須 先 判 斷 變 數 當 時 的 資 料 型 態 , 無 形 中 增 加 了 執 行 上 的 負 擔 , 除 此 之 外 , 當 變 數 的 資 料 型 態 改 變 時 , 例 如 前 面 例 子 中 的 X 由 整 數 型 態 變 成 字 串 型 態 , 它 所 佔 用 的 記 憶 體 大 小 可 能 與 原 資 料 型 態 所 佔 用 的 記 憶 體 大 小 不 同 , 因 此 必 須 先 釋 放 原 記 憶 體 , 再 重 新 配 置 新 記 憶 體 , 這 又 得 增 加 記 憶 體 管 理 上 的 負 擔 。 不 過 您 也 別 把 它 想 得 太 糟 糕 , 上 述 的 「 額 外 負 擔 」 對 大 部 份 的 應 用 程 式 來 說 , 還 不 至 於 使 執 行 速 度 緩 慢 到 不 能 接 受 的 地 步 , 加 上 硬 體 的 技 術 越 來 越 進 步 , CPU 的 速 度 越 來 越 快 , 使 得 這 個 缺 點 不 再 那 麼 舉 足 輕 重 了 。 反 觀 它 有 什 麼 優 點 呢 ? 首 先 是 它 比 較 接 近 人 類 的 思 考 模 式 , 誰 規 定 變 數 一 旦 被 指 定 成 某 一 種 資 料 型 態 後 , 就 不 能 改 變 了 , 所 以 變 數 的 型 態 可 以 變 來 變 去 本 來 就 比 較 容 易 使 用 。 除 此 之 外 , 它 比 定 型 變 數 更 容 易 應 用 到 物 件 導 向 的 程 式 中 , 舉 個 例 子 : 假 設 我 們 在 程 式 中 會 使 用 到 許 多 物 件 , 而 這 些 物 件 各 具 有 不 同 的 資 料 型 態 , 為 了 管 理 這 些 物 件 , 我 們 想 須 把 它 們 集 中 放 在 一 起 , 其 中 最 簡 便 的 方 式 就 是 採 用 陣 列 , 所 以 程 式 可 能 是 : ObjArr(1) = 物 件 _1 ObjArr(2) = 物 件 _2 ... ObjArr(N) = 物 件 _N 由 於 ObjArr 是 一 個 陣 列 , 假 如 我 們 把 它 宣 告 成 「 定 型 變 數 」 , 那 麼 該 定 義 成 物 件 _1 的 資 料 型 態 、 還 是 物 件 _2 的 資 料 型 態 , 還 是 … , 顯 然 那 是 行 不 通 的 , 如 果 把 這 個 陣 列 宣 告 成 「 多 型 變 數 」 , 陣 列 中 的 每 一 個 元 素 就 可 以 依 程 式 執 行 時 的 狀 況 來 改 變 其 資 料 型 態 , 自 然 而 然 就 滿 足 了 這 個 需 求 。 註 : C++ 並 不 具 有 此 類 型 的 變 數 , 為 了 因 應 上 述 的 需 求 , C++ 使 用 了 「 繼 承 」 ( inheritance) 、 「 函 數 多 型 」 ( polymorphism) 及 「 指 標 」 的 技 術 來 完 成 上 述 的 目 的 , 但 使 用 上 並 不 自 然 , 也 不 易 學 習 。 |
有 些 程 式 語 言 允 許 多 個 不 同 名 稱 的 變 數 參 考 到 同 一 塊
記 憶 體 , 假 設 X 與 Y 是 兩 個 參 考 到 同 一 塊 記 憶 體 的 變 數 ,
則 它 們 的 關 係 可 以 表 達 成 :
圖 -5 兩 個 變 數 使 用 到 同 一 塊 記 憶 體 從 上 圖 來 看 , X 與 Y 的 位 址 是 相 同 的 , 所 以 我 把 它 們 稱 為 同 址 變 數 。 同 址 變 數 除 了 名 稱 不 同 之 外 , 實 質 上 它 們 指 的 是 同 樣 的 東 西 , 因 為 既 然 它 們 參 考 到 同 一 塊 記 憶 體 , 所 以 只 要 其 中 一 個 同 址 變 數 的 值 被 改 變 了 , 其 他 同 址 變 數 的 值 也 會 跟 著 改 變 , 以 FORTRAN 為 例 , 乾 脆 就 用 EQUIVALENCE ( 相 等 ) 的 字 眼 來 表 達 同 址 變 數 , 其 語 法 是 EQUIVALENCE (X, Y), 表 示 變 數 X 與 變 數 Y 是 相 等 的 兩 個 變 數 。 C++ 則 直 接 採 用 代 表 位 址 的 & 符 號 來 表 示 這 層 關 係 , 其 方 法 是 先 宣 告 一 個 一 般 的 變 數 , 假 設 是 X, 然 後 再 宣 告 另 一 個 同 址 變 數 , 假 設 是 Y, 並 且 將 它 的 位 址 設 定 成 X 的 位 址 , 實 例 如 下 : int X; // 先 宣 告 一 個 一 般 的 變 數 int &Y = X; // 宣 告 同 址 變 數 Y, 使 它 的 位 址 (&Y) 等 於 X 的 位 址 Y 是 X 的 同 址 變 數 , 另 一 個 比 較 通 俗 的 說 詞 是 : Y 是 X 的 別 名 ( alias) , 這 就 好 像 古 時 候 的 人 , 除 了 取 "名" 之 外 還 要 有 "字" , 例 如 關 羽 , 字 雲 長 , 關 雲 長 與 關 羽 其 實 就 是 同 一 個 人 。 使 用 同 址 變 數 並 不 是 好 的 習 慣 , 因 為 變 數 值 的 改 變 往 往 是 因 為 另 一 個 同 址 變 數 的 值 被 改 變 的 關 係 , 這 使 得 我 們 不 容 易 追 蹤 到 變 數 的 變 化 情 形 , 徒 增 偵 錯 的 困 難 。 其 實 同 址 變 數 主 要 是 應 用 在 副 程 式 的 「 參 數 傳 遞 」 上 , 這 我 們 將 在 稍 後 會 有 所 說 明 。 |
使 用 指 標 變 數 最 有 名 的 程 式 語 言 首 推 C , 而 且 也 是 最
容 易 讓 人 弄 錯 , 最 不 容 易 學 習 的 地 方 , 不 過 若 從 變 數 的
四 個 組 成 元 素 來 理 解 它 , 您 將 會 發 現 它 其 實 一 點 也 不 難
。
首 先 要 確 立 一 個 觀 念 , 那 就 是 指 標 變 數 和 其 他 變 數 一 樣 包 含 變 數 的 四 個 元 素 , 最 大 的 不 同 點 則 在 於 指 標 變 數 的 值 所 代 表 的 不 是 單 純 的 「 值 」 , 而 是 另 一 塊 記 憶 體 的 「 位 址 」 , 以 式 子 「 char *ptr;」 為 例 , 表 達 成 變 數 的 四 元 素 是 : 圖 -6 指 標 變 數 的 值 代 表 的 是 另 一 塊 記 憶 體 的 位 址 請 注 意 一 件 事 , 虛 線 下 的 記 憶 體 並 不 是 歸 變 數 ptr 所 有 , 它 可 能 是 : (1) 另 一 個 變 數 的 記 憶 體 , (2)另 一 塊 透 過 配 置 函 數 ( 例 如 malloc()) 所 配 置 出 來 的 記 憶 體 , (3)另 一 塊 我 們 不 該 使 用 的 記 憶 體 。 在 進 一 步 的 解 說 以 前 , 我 們 要 先 複 習 一 下 指 標 變 數 在 C 語 言 中 的 語 法 。 指 標 變 數 的 語 法 與 其 他 變 數 最 大 的 不 同 在 於 它 使 用 了 & 及 * 符 號 , 以 下 是 幾 個 例 子 : ( 例 子 一 ) char *ptr; ( 例 子 二 ) char ch; ptr = &ch; ( 例 子 三 ) *ptr = 'A'; 例 子 一 的 * 只 是 用 來 宣 告 變 數 ptr 是 一 個 指 標 變 數 。 例 子 二 的 & 是 用 來 取 變 數 ch 的 位 址 , 所 以 經 過 「 ptr = &ch;」 的 式 子 後 , 我 們 可 以 把 ptr 與 ch 的 關 係 表 示 成 : 圖 -7 ptr = &ch; 所 產 生 的 結 果 注 意 上 圖 的 &ch 表 示 ch 的 位 址 , 而 把 ch 的 位 址 (&ch)指 定 給 指 標 變 數 ptr 將 使 得 ptr 指 向 ch 的 記 憶 體 。 例 子 三 的 *ptr 則 表 示 ptr 所 指 到 的 記 憶 體 , 承 例 子 二 , 等 於 ch 的 記 憶 體 , 而 「 *ptr = 'A';」 表 示 把 'A' 複 製 到 ptr 所 指 到 的 記 憶 體 中 , 也 就 是 ch 的 記 憶 體 中 , 如 下 圖 : 圖 -8 *ptr = 'A'; 所 產 生 的 結 果 正 常 的 情 況 下 , 指 標 變 數 所 指 向 的 位 址 是 一 個 「 另 一 個 變 數 的 位 址 」 、 或 是 一 塊 「 配 置 出 來 的 記 憶 體 」 , 例 子 二 中 的 「 ptr = &ch;」 即 是 讓 指 標 變 數 ptr 指 向 變 數 ch 的 位 址 , 至 於 讓 變 數 指 到 一 塊 「 配 置 出 來 的 記 憶 體 」 常 見 的 例 子 如 下 : char *ptr; ptr = malloc(100); // 指 標 變 數 ptr 指 向 一 塊 大 小 為 100 的 記 憶 體 圖 -9 指 標 變 數 ptr 的 值 指 向 一 塊 大 小 為 100 的 記 憶 體 請 注 意 這 塊 大 小 等 於 100 的 記 憶 體 , 並 不 像 變 數 一 樣 有 名 稱 , 因 此 要 使 用 到 它 必 須 藉 助 ptr 的 指 標 功 能 , 請 注 意 以 下 是 一 個 使 用 配 置 記 憶 體 的 錯 誤 例 子 : ptr = malloc(100); ... ptr = &ch; 這 個 例 子 中 的 ptr 原 本 是 指 到 一 塊 被 配 置 出 來 的 記 憶 體 , 但 是 經 過 「 ptr = &ch;」 的 式 子 後 , ptr 指 向 變 數 ch 的 記 憶 體 , 使 得 原 先 經 malloc() 所 配 置 出 來 的 記 憶 體 變 成 「 無 頭 」 記 憶 體 ( 原 先 的 頭 是 ptr) , 形 成 了 記 憶 體 浪 費 的 現 象 , 正 確 的 用 法 是 在 「 ptr = &ch;」 前 面 加 上 釋 放 記 憶 體 的 函 數 , 如 下 : free( ptr ); // 釋 放 ptr 所 指 到 的 記 憶 體 ptr = &ch; 當 然 C 語 言 的 指 標 變 數 還 有 多 種 變 化 , 例 如 :
但 是 概 念 上 , 仍 然 可 以 用 變 數 的 四 元 素 來 解 釋 它 們 , 在 此 無 法 一 一 列 舉 , 想 進 一 步 研 究 C 語 言 指 標 變 數 的 各 種 變 化 , 請 再 閱 讀 C 語 言 的 相 關 書 籍 。 指 標 變 數 可 以 說 是 C 程 式 的 BUG 之 源 , 以 下 列 出 兩 種 狀 況 , 旨 在 說 明 指 標 變 數 產 生 錯 誤 的 根 本 原 因 。 當 我 們 只 是 這 樣 宣 告 時 : int *ptr; 我 們 並 未 對 ptr 的 「 值 」 設 定 初 值 , 因 此 我 們 並 不 知 道 他 會 指 向 哪 一 個 位 址 , 所 以 緊 接 著 以 下 的 指 定 動 作 是 很 危 險 的 : *ptr = 123; // 將 123 寫 入 ptr 指 向 的 位 址 , // 但 那 一 個 位 址 的 記 憶 體 是 我 們 該 改 變 的 嗎 ? // 如 果 該 位 址 底 下 是 系 統 的 程 式 碼 , 會 怎 樣 呢 ? // 如 果 是 其 他 程 式 的 資 料 , 又 會 怎 樣 呢 ? 另 一 個 情 況 是 指 標 變 數 在 運 算 的 過 程 中 , 指 向 資 料 的 緩 衝 區 之 外 了 , 例 如 : char buf[10]; char *ptr = buf; strcpy( ptr, "RUN!程 式 設 計 " ); // 複 製 字 串 "RUN!程 式 設 計 " 到 ptr 所 指 到 的 緩 衝 區 內 這 個 例 子 中 , 由 於 ptr 所 指 到 的 緩 衝 區 buf 其 大 小 只 由 10-bytes, 而 複 製 進 來 的 字 串 卻 有 13-bytes, 這 使 得 字 串 被 複 製 到 buf 之 外 了 , 顯 然 它 會 破 壞 到 某 一 塊 記 憶 體 。 假 如 被 破 壞 的 記 憶 體 剛 好 沒 人 使 用 , 上 述 的 破 壞 行 為 也 許 不 會 使 程 式 產 生 異 常 的 現 象 ; 但 它 也 可 能 破 壞 了 自 己 程 式 的 資 料 區 , 使 得 某 些 關 鍵 性 的 變 數 莫 名 其 妙 地 變 了 , 那 將 使 程 式 難 以 偵 錯 ; 當 然 還 可 能 破 壞 別 人 或 系 統 的 程 式 , 其 結 果 就 更 難 預 測 了 。 因 此 利 用 指 標 變 數 應 該 特 別 防 止 指 標 變 數 指 到 不 應 該 的 地 方 , 以 strcpy() 的 例 子 來 看 , 改 成 以 下 式 子 比 較 好 : strncpy( ptr, "RUN!程 式 設 計 ", 10 ); // 當 複 製 長 度 達 到 10 時 , 停 止 複 製 指 標 變 數 的 使 用 給 予 程 式 設 計 相 當 大 的 方 便 性 , 但 是 缺 點 就 是 指 標 變 數 可 能 因 為 程 式 上 的 BUG 而 指 到 不 應 該 的 地 方 , 這 通 常 是 程 式 當 掉 的 主 要 原 因 , 因 此 使 用 指 標 變 數 時 不 得 不 小 心 這 方 面 的 問 題 。 |
猶 如 社 會 的 分 工 一 樣 , 在 程 式 的 撰 寫 過 程 中 , 我 們 往
往 也 會 把 大 程 式 分 成 一 個 個 的 副 程 式 , 每 個 副 程 式 各 自
負 責 某 個 或 某 些 功 能 , 然 後 再 利 用 主 程 式 把 這 些 副 程 式
串 連 起 來 , 以 達 成 我 們 期 望 的 目 的 , 而 參 數 的 傳 遞 則 是
串 連 所 有 副 程 式 及 主 程 式 的 介 面 。
呼 叫 副 程 式 傳 遞 參 數 在 程 式 語 言 中 最 常 見 的 方 式 有 兩 種 : 「 傳 值 呼 叫 」 ( call-by-value) 及 「 傳 位 址 呼 叫 」 ( call-by-address) , 這 兩 種 參 數 的 傳 遞 方 式 在 表 面 上 很 容 易 讓 人 忽 略 其 差 異 性 , 但 實 際 上 卻 會 影 響 程 式 執 行 的 結 果 , 以 下 先 以 一 個 QBASIC 的 例 子 來 說 明 這 個 問 題 : 假 設 有 一 副 程 式 定 義 如 下 : Sub AddOne( x ) x = x + 1 End Sub 而 當 呼 叫 的 方 法 如 下 時 : i = 10 Call AddOne( i ) Print i 其 輸 出 結 果 是 : 11。 但 是 當 呼 叫 的 方 法 是 : i = 10 Call AddOne( (i) ) ' 注 意 : 參 數 i 的 前 後 加 了 () Print I 則 輸 出 結 果 是 : 10。 上 述 例 子 會 有 不 同 的 輸 出 結 果 , 是 因 為 參 數 傳 遞 方 式 有 所 不 同 的 關 係 , 以 下 我 們 除 了 要 解 說 上 述 兩 種 參 數 傳 遞 上 的 差 異 及 原 理 之 外 , 還 會 比 較 它 們 的 優 缺 點 。 首 先 讓 我 們 從 參 數 的 基 本 運 作 開 始 : |
當 我 們 定 義 副 程 式 時 , 例 如 前 面 的 Sub AddOne( x ), 由 於
我 們 並 不 知 道 將 來 呼 叫 者 會 傳 入 哪 一 個 變 數 作 為 參 數 ,
因 此 只 好 給 這 個 參 數 取 一 個 暫 時 性 的 名 稱 , 例 如 本 例 的
x, 而 將 來 副 程 式 被 呼 叫 時 它 們 會 被 真 正 的 變 數 所 取 代 ,
所 以 這 些 參 數 都 只 是 「 型 式 上 」 的 , 故 稱 之 為 「 型 式 參
數 」 ( formal parameter) 。
而 呼 叫 程 式 在 呼 叫 副 程 式 時 , 必 須 以 實 際 的 資 料 來 替 代 型 式 上 的 參 數 , 使 得 副 程 式 能 夠 拿 到 真 正 的 資 料 來 運 算 , 這 真 正 的 資 料 就 叫 做 「 實 際 參 數 」 ( actual parameter) , 例 如 Call AddOne( i ) 中 的 i。 所 謂 參 數 傳 遞 的 方 式 , 就 是 在 呼 叫 副 程 式 的 過 程 中 , 編 譯 器 ( 或 解 譯 器 ) 如 何 以 實 際 參 數 來 替 代 型 式 參 數 的 方 法 , 而 方 法 不 同 , 得 到 的 結 果 也 可 能 不 同 , 這 是 我 們 每 次 接 觸 新 的 程 式 語 言 所 必 須 注 意 的 事 情 。 解 說 參 數 的 傳 遞 方 式 以 前 , 讓 我 們 先 以 前 面 所 介 紹 過 的 「 變 數 的 四 個 元 素 」 來 表 示 實 際 參 數 及 型 式 參 數 , 就 拿 前 面 QBASIC 的 程 式 做 例 子 吧 : 圖 -10 呼 叫 副 程 式 時 的 參 數 傳 遞 就 上 圖 來 看 , 變 數 有 四 個 元 素 , 所 謂 傳 遞 參 數 , 傳 的 到 底 是 名 稱 、 資 料 型 態 、 位 址 、 還 是 值 呢 ? 其 實 除 了 傳 遞 資 料 型 態 無 法 達 到 傳 遞 參 數 的 目 的 之 外 , 其 他 三 個 元 素 都 可 能 作 為 參 數 傳 遞 的 標 的 物 , 不 過 以 名 稱 來 傳 遞 的 方 式 容 易 引 起 混 淆 , 而 且 只 有 極 少 數 的 程 式 語 言 支 援 , 我 們 在 此 並 不 討 論 , 以 下 只 就 傳 「 值 」 及 傳 「 位 址 」 兩 種 方 式 來 加 以 解 說 。 |
這種傳遞參數的方式,是把呼叫者的實際參數值,複製一份到副程式的型 式參數位址內,而後副程式不管怎麼對型式參數進行運算,完全不會對實
際參數的變數值有所影響。
圖-11 傳值呼叫:複製實際參數值到型式參數 以前面的 QBASIC 例子來說,呼叫者在實際參數的前後加上 (),例如 Call AddOne( (i) ),其傳遞方式就是傳值呼叫,所以以下的呼叫方式 : i = 10 Call AddOne( (i) ) Print i 變數 i 的值不會因執行了 AddOne() 而被改變,因此輸出結果等於 10。 傳值呼叫的好處是傳入的實際參數不會遭到破壞,不過它也有相對的缺點 ,想想假如傳入的參數是一個很大的陣列,那麼複製參數時難免要浪費點 時間。 |
所 謂 傳 值 呼 叫 , 我 們 可 以 把 它 想 成 「 以 實 際 參 數 的 值
取 代 型 式 參 數 的 值 」 , 而 傳 位 址 呼 叫 則 是 「 以 實 際 參 數
的 位 址 取 代 型 式 參 數 的 位 址 」 , 示 意 圖 如 下 :
圖 -12 傳 位 址 呼 叫 : 實 際 參 數 與 型 式 參 數 彼 此 是 同 址 變 數 從 上 圖 來 看 , 我 們 可 以 把 傳 位 址 呼 叫 中 的 實 際 參 數 與 型 式 參 數 想 成 兩 個 「 同 址 變 數 」 , 同 址 變 數 的 特 性 是 只 要 其 中 有 一 個 變 數 的 值 被 改 變 , 其 它 的 同 址 變 數 也 會 跟 著 改 變 。 以 前 面 的 QBASIC 例 子 來 看 , Call AddOne( i ) 由 於 未 在 變 數 i 的 前 後 再 加 上 (), 被 視 為 傳 位 址 呼 叫 , 因 此 呼 叫 之 後 的 i 值 將 因 為 型 式 參 數 x( 與 i 是 同 址 變 數 ) 的 改 變 而 跟 著 改 變 , 所 以 以 下 的 式 子 : i = 10 Call AddOne( i ) Print i 其 輸 出 結 果 等 於 11。 傳 位 址 呼 叫 的 好 處 與 傳 值 呼 叫 是 相 對 的 , 當 參 數 是 一 個 大 的 陣 列 或 資 料 結 構 時 , 在 不 必 複 製 大 量 資 料 之 下 , 才 會 顯 得 出 它 的 優 點 。 至 於 它 最 大 的 缺 點 是 它 的 實 際 參 數 值 可 能 被 副 程 式 改 變 , 使 得 程 式 設 計 人 員 容 易 在 不 知 不 覺 之 中 忽 略 了 這 件 事 情 , 造 成 偵 錯 上 的 一 個 盲 點 , 當 然 這 也 是 我 們 特 別 針 對 傳 位 址 呼 叫 提 出 討 論 的 原 因 。 |
雖 然 各 種 程 式 語 言 在 參 數 傳 遞 的 原 理 上 都 是 相 似 的 ,
但 語 法 上 或 有 不 同 , 以 下 特 別 針 對 幾 個 常 見 的 程 式 語 言
提 出 說 明 , 謹 供 參 考 。
QBASIC: 決 定 傳 值 呼 叫 或 傳 位 址 呼 叫 的 權 利 在 於 呼 叫 端 , 例 如 前 面 例 子 中 的 Call AddOne( i ) 表 示 傳 位 址 呼 叫 , 而 Call AddOne( (i) ) 表 示 傳 值 呼 叫 。 Visual Basic: 承 襲 QBASIC 既 有 的 語 法 , 可 在 呼 叫 端 決 定 傳 值 呼 叫 或 傳 位 址 呼 叫 , 除 此 之 外 , 它 還 允 許 在 副 程 式 端 決 定 參 數 傳 遞 的 方 式 , 當 我 們 在 副 程 式 的 型 式 參 數 前 面 加 上 ByVal 時 , 例 如 : Sub AddOne( ByVal x ) 表 示 不 管 呼 叫 端 是 否 在 在 實 際 參 數 的 前 後 加 上 (), 一 概 視 為 傳 值 呼 叫 , 但 如 果 副 程 式 沒 有 在 型 式 參 數 前 面 加 上 ByVal, 則 參 數 的 傳 遞 方 式 由 呼 叫 端 來 決 定 。 PASCAL: 決 定 參 數 傳 遞 方 式 者 是 副 程 式 , 在 語 法 上 , 當 副 程 式 宣 告 時 在 參 數 前 面 加 上 var, 表 示 傳 位 址 呼 叫 , 例 如 : procedure AddOne ( var x : integer ); 而 沒 有 加 var 時 表 示 傳 值 呼 叫 , 例 如 : procedure AddOne ( x : integer ); C : C 語 言 是 十 分 特 殊 的 一 個 , 在 參 數 傳 遞 的 機 制 上 , 它 只 提 供 傳 值 呼 叫 , 但 是 在 語 法 上 他 則 提 供 了 一 種 傳 位 址 呼 叫 的 變 形 。 承 續 前 面 的 AddOne 例 子 , 如 果 我 們 在 副 程 式 的 宣 告 上 這 麼 寫 : AddOne( int x ); 那 麼 當 我 們 這 樣 以 式 子 AddOne( i ) 呼 叫 副 程 式 時 , 編 譯 程 式 採 傳 值 呼 叫 , i 的 值 一 定 不 會 被 改 變 , 如 果 我 們 想 改 變 實 際 參 數 i 的 值 , 在 副 程 式 端 及 呼 叫 端 分 別 要 改 成 : 副 程 式 : AddOne( int *x ); /* 副 程 式 的 型 式 參 數 必 須 使 用 指 標 變 數 */ 呼 叫 端 : AddOne( &i ); /* 呼 叫 端 必 須 傳 入 實 際 參 數 的 位 址 */ 請 注 意 這 仍 然 不 是 傳 位 址 呼 叫 , 它 的 作 法 只 是 以 實 際 參 數 的 位 址 取 代 宣 告 成 指 標 變 數 的 型 式 參 數 罷 了 , 其 示 意 圖 如 下 : 圖 -13 C 語 言 中 以 指 標 變 數 作 為 型 式 參 數 來 達 成 傳 位 址 的 功 效 C++: 身 為 C 的 下 一 代 , C++ 在 參 數 的 傳 遞 上 增 加 了 傳 位 址 的 功 能 , 其 語 法 是 : 呼 叫 端 : AddOne( i ); // 維 持 一 般 的 語 法 副 程 式 : AddOne( int &x ); // 型 式 參 數 前 面 加 上 & 符 號 |
記 得 以 前 看 過 一 種 在 湖 中 抓 鴨 子 的 比 賽 , 規 則 很 簡 單
, 鴨 子 被 放 入 湖 中 之 後 , 就 到 處 悠 游 , 而 競 賽 者 只 能 徒
手 抓 鴨 , 結 果 經 常 看 到 好 像 快 到 手 的 鴨 子 很 輕 易 地 就 溜
走 了 , 十 分 有 趣 。
如 果 我 們 把 程 式 當 作 一 潭 湖 水 , 變 數 倒 像 是 湖 裡 面 悠 游 的 鴨 子 , 在 程 式 的 執 行 中 , 我 們 期 望 變 數 能 夠 完 全 依 照 我 們 的 意 思 來 變 化 , 就 像 我 們 期 望 鴨 子 乖 乖 地 被 我 們 抓 到 一 樣 。 但 怎 樣 讓 鴨 子 乖 乖 地 就 擒 呢 ? 而 程 式 中 , 怎 樣 抓 到 變 數 的 變 化 呢 ? 假 設 說 您 的 程 式 有 五 十 個 副 程 式 , 而 您 想 抓 住 的 變 數 都 可 能 出 現 這 五 十 個 副 程 式 中 , 那 麼 您 除 了 必 須 檢 視 這 五 十 個 副 程 式 之 外 , 還 要 防 範 變 數 在 這 五 十 個 副 程 式 中 溜 來 溜 去 ( 因 為 每 一 個 副 程 式 都 可 能 改 變 這 個 變 數 的 值 ) , 就 像 鴨 子 會 在 湖 中 溜 來 溜 去 一 樣 。 為 了 快 速 地 抓 到 湖 中 的 鴨 子 , 我 們 會 把 鴨 子 趕 到 湖 的 角 落 ; 為 了 快 速 地 抓 到 變 數 的 變 化 , 我 們 則 希 望 變 數 的 活 動 範 圍 越 小 越 好 , 於 是 有 了 「 區 域 變 數 」 的 觀 念 。 |
所 謂 「 區 域 變 數 」 指 的 就 是 只 能 在 某 一 個 範 圍 內 活 動
的 變 數 , 最 常 見 的 例 子 是 副 程 式 內 宣 告 的 變 數 , 如 :
Sub DoSomething() Dim x ' 變 數 x 是 副 程 式 內 的 區 域 變 數 ... End Sub 變 數 x 的 活 動 範 圍 就 只 侷 限 在 副 程 式 之 內 , 其 他 地 方 若 想 使 用 這 個 變 數 是 絕 對 辦 不 到 的 , 除 非 那 個 地 方 也 宣 告 了 一 個 叫 做 x 的 變 數 , 不 過 那 個 地 方 的 x 與 這 個 副 程 式 的 x 可 是 完 全 不 相 關 的 兩 個 變 數 。 有 些 程 式 語 言 除 了 以 副 程 式 來 限 制 變 數 的 活 動 範 圍 之 外 , 還 提 供 「 區 塊 」 的 功 能 , 凡 是 在 區 塊 內 所 宣 告 的 區 域 變 數 就 只 能 在 這 個 區 塊 內 使 用 , 即 使 是 同 一 個 副 程 式 內 的 其 他 區 塊 也 不 能 使 用 它 , 改 良 版 的 C 及 C++ 就 有 這 樣 的 功 能 , 例 如 : RoutineX() { // 副 程 式 區 塊 int x; // 變 數 x 可 以 在 副 程 式 的 任 何 地 方 被 使 用 , // 包 含 區 塊 1 與 區 塊 2 ... { // 區 塊 1 int x1; // 變 數 x1 只 能 在 區 塊 1 之 內 被 使 用 } ... { // 區 塊 2 int x2; // 變 數 x2 只 能 在 區 塊 2 之 內 被 使 用 } } 上 面 這 個 例 子 , 您 還 可 以 發 現 區 塊 是 有 層 次 的 , 例 如 區 塊 1 與 區 塊 2 是 平 行 的 兩 個 區 塊 , 彼 此 的 變 數 是 互 相 獨 立 的 , 但 是 「 副 程 式 區 塊 」 卻 是 區 塊 1 與 區 塊 2 的 上 一 層 , 所 以 副 程 式 內 的 變 數 x 可 以 在 區 塊 1 及 區 塊 2 內 使 用 。 在 多 層 區 塊 中 , 下 層 的 變 數 名 稱 可 以 與 上 層 的 變 數 名 稱 相 同 , 例 如 以 下 例 子 的 x: { // 第 一 層 區 塊 int x; int y; ... { // 第 二 層 區 塊 int x; // x 的 名 稱 也 出 現 在 上 一 層 區 塊 int z; ... z = 10; // 使 用 本 區 塊 的 z x = 20; // 使 用 本 區 塊 的 x y = 30; // 使 用 第 一 層 區 塊 的 y } x = 100; // 使 用 第 一 層 區 塊 的 x } 對 編 譯 器 來 說 , 當 程 式 中 的 某 一 個 式 子 使 用 到 變 數 時 , 它 會 先 在 該 式 子 所 屬 的 區 塊 內 搜 尋 是 否 有 這 個 變 數 存 在 , 如 果 是 , 則 使 用 它 , 否 則 持 續 向 上 一 層 區 塊 尋 找 , 直 到 找 到 為 止 , 所 以 式 子 「 z = 10;」 及 「 x = 20;」 會 使 用 到 第 二 層 區 塊 的 z 及 x 變 數 , 而 「 y = 30;」 及 「 x = 100;」 則 會 使 用 第 一 層 的 y 及 x 變 數 。 區 域 變 數 的 好 處 除 了 可 以 限 制 它 們 的 活 動 範 圍 之 外 , 另 一 點 就 是 不 必 擔 心 變 數 名 稱 的 重 複 的 問 題 , 如 上 面 例 子 的 兩 個 變 數 x, 這 使 得 我 們 可 以 將 大 程 式 分 成 幾 個 不 同 的 模 組 來 開 發 , 每 個 模 組 各 有 負 責 的 程 式 設 計 師 , 而 每 位 程 式 設 計 師 可 以 按 照 自 己 的 喜 好 為 程 式 中 的 變 數 命 名 , 不 必 擔 心 與 他 人 的 變 數 名 稱 相 衝 突 。 |
什 麼 是 全 域 變 數 ? 可 以 悠 游 在 程 式 的 任 何 一 個 地 方 的
變 數 就 是 全 域 變 數 , 也 就 是 說 , 不 管 主 程 式 或 任 何 一 個
副 程 式 、 區 塊 都 可 以 使 用 的 變 數 , 由 於 它 像 是 湖 中 到 處
悠 游 的 鴨 子 , 不 容 易 抓 到 , 所 以 會 增 加 偵 錯 上 的 難 度 ,
不 過 有 些 時 候 我 們 還 是 不 得 不 使 用 它 , 舉 例 來 說 , 您 想
要 為 收 費 站 寫 一 個 收 費 情 形 的 程 式 , 其 中 一 個 副 程 式 在
每 過 一 輛 車 時 , 將 計 數 器 ( 假 設 以 變 數 counter 代 表 ) 加
一 , 而 另 一 個 副 程 式 則 會 每 隔 一 小 時 抓 取 計 數 器 的 值 ,
並 且 將 它 列 印 到 報 表 上 , 因 此 這 兩 個 副 程 式 都 必 須 使 用
到 同 一 個 變 數 counter, 但 不 管 我 們 把 這 個 變 數 放 在 哪 一 個
副 程 式 裡 面 , 另 一 個 副 程 式 都 無 法 使 用 , 所 以 就 必 須 藉
助 全 域 變 數 了 。
就 全 域 變 數 的 語 法 來 說 , 它 會 被 放 在 放 在 副 程 式 之 外 , 例 如 C 語 言 中 您 如 果 這 樣 宣 告 , 就 可 以 使 變 數 變 成 全 域 變 數 : int counter; // 放 在 副 程 式 之 外 , 就 變 成 全 域 變 數 了 Increment() { counter = counter + 1; } Report() { printf( "車 輛 數 =%d", counter ); } 全 域 變 數 的 確 提 供 給 副 程 式 之 間 一 個 簡 便 的 共 用 資 料 方 法 , 不 過 筆 者 不 禁 要 再 提 醒 您 一 次 , 全 域 變 數 會 使 偵 錯 變 得 比 較 困 難 , 因 此 如 果 您 真 的 非 用 它 不 可 , 記 得 做 好 文 件 , 而 文 件 中 至 少 要 包 括 哪 些 副 程 式 使 用 了 它 、 以 及 這 些 副 程 式 是 否 會 改 變 它 們 的 值 。 |
當 兩 個 以 上 的 副 程 式 必 須 共 用 資 料 時 , 使 用 全 域 變 數
是 無 可 避 免 的 , 模 組 變 數 乃 是 針 對 全 域 變 數 的 一 種 改 良
。 所 謂 全 域 變 數 指 的 是 「 全 程 式 」 皆 可 使 用 的 變 數 , 所
謂 模 組 變 數 , 則 是 指 「 同 一 模 組 」 內 所 有 副 程 式 皆 可 使
用 ( 但 其 他 模 組 不 可 使 用 ) 的 變 數 。
所 謂 同 一 模 組 在 傳 統 的 程 式 語 言 中 , 通 常 指 的 是 同 一 個 檔 案 , 舉 例 來 說 , 前 面 收 費 站 的 C 程 式 , 您 如 果 把 Increment() 及 Report() 放 在 同 一 個 檔 案 中 , 這 兩 個 副 程 式 就 屬 於 同 一 個 模 組 。 最 近 大 家 常 聽 到 的 視 覺 化 ( visual) 程 式 語 言 , 模 組 可 能 是 一 個 檔 案 、 一 個 表 格 、 或 一 個 物 件 類 別 等 。 為 了 改 良 全 域 變 數 的 缺 點 , 我 們 會 盡 量 把 使 用 到 相 同 全 域 變 數 的 副 程 式 放 在 同 一 個 模 組 內 , 然 後 把 全 域 變 數 改 成 模 組 變 數 , 使 其 他 模 組 無 法 使 用 到 這 個 變 數 。 以 前 面 收 費 站 的 例 子 來 看 , 其 作 法 是 把 Increment() 及 Report() 兩 個 副 程 式 放 在 同 一 個 檔 案 , 然 後 在 counter 變 數 的 前 面 加 上 static 的 字 眼 , 如 下 : static int counter; // C 語 言 中 , 在 全 域 變 數 之 前 加 上 // static 可 將 變 數 改 成 模 組 變 數 Increment() { counter = counter + 1; } Report() { printf( "車 輛 數 =%d", counter ); } 請 注 意 加 static 是 C 語 言 中 將 全 域 變 數 改 成 模 組 變 數 的 方 法 , 但 是 每 一 種 程 式 語 言 如 何 指 定 全 域 變 數 及 模 組 變 數 , 其 方 法 未 必 與 C 語 言 相 同 , 例 如 Visual Basic 中 , 如 果 您 把 變 數 放 在 副 程 式 之 外 , 並 且 在 宣 告 中 加 了 GLOBAL 的 字 眼 , 那 麼 變 數 是 全 域 變 數 , 如 果 只 是 把 變 數 放 在 副 程 式 之 外 , 那 麼 變 數 就 只 是 模 組 變 數 。 |
變 數 的 生 命 週 期 指 的 是 變 數 什 麼 時 候 被 產 生 出 來 , 以
及 什 麼 時 候 消 失 掉 , 但 變 數 是 怎 麼 產 生 出 來 的 , 又 是 怎
麼 消 失 的 呢 ? 讓 我 們 再 次 回 顧 變 數 的 四 個 元 素 , 這 次 要
比 較 變 數 在 「 編 碼 階 段 」 及 「 執 行 階 段 」 的 差 異 。 當 我
們 在 編 碼 階 段 宣 告 一 個 變 數 時 , 由 於 還 沒 執 行 , 所 以 變
數 並 沒 有 佔 用 系 統 的 記 憶 體 , 以 四 個 元 素 來 看 , 它 還 不
包 含 「 位 址 」 及 「 值 」 , 直 到 程 式 開 始 執 行 , 而 系 統 為
變 數 配 置 了 記 憶 體 之 後 , 變 數 才 真 正 的 包 含 了 四 個 元 素
, 下 圖 可 以 表 示 變 數 在 這 兩 個 階 段 上 的 差 異 :
圖 -14 變 數 在 編 碼 階 段 及 執 行 階 段 的 比 較 所 謂 變 數 有 了 生 命 指 的 就 是 系 統 為 變 數 配 置 了 一 塊 記 憶 體 , 而 當 系 統 把 變 數 的 記 憶 體 收 回 時 , 就 是 變 數 的 生 命 結 束 時 , 所 以 當 程 式 結 束 執 行 , 程 式 內 所 有 變 數 的 生 命 也 會 跟 著 結 束 , 至 於 編 碼 階 段 , 變 數 是 可 說 是 根 本 就 沒 有 生 命 的 。 對 於 全 域 變 數 來 說 , 因 為 所 有 的 副 程 式 都 可 能 使 用 到 它 們 , 所 以 在 程 式 被 載 入 系 統 時 , 它 們 的 記 憶 體 就 被 配 置 出 來 了 , 也 就 是 說 全 域 記 憶 體 的 生 命 起 於 程 式 的 開 始 執 行 , 直 到 程 式 結 束 時 , 它 們 的 生 命 才 會 跟 著 結 束 。 |
對 於 區 域 變 數 來 說 , 它 的 生 命 起 於 區 塊 程 式 碼 開 始 被
執 行 時 , 結 束 於 程 式 的 執 行 過 了 區 塊 時 , 例 如 :
為 什 麼 系 統 處 理 區 域 變 數 的 生 命 週 期 要 這 麼 麻 煩 呢 ? 舉 個 例 子 來 說 吧 ! 假 設 我 們 寫 了 一 個 程 式 , 其 中 包 含 了 100 個 副 程 式 , 而 平 均 每 個 副 程 式 會 使 用 到 1 KB 的 變 數 , 如 果 系 統 採 取 程 式 一 載 入 就 為 所 有 區 域 變 數 配 置 好 記 憶 體 的 策 略 , 那 麼 程 式 就 必 須 馬 上 吃 掉 100 KB 的 記 憶 體 , 但 實 際 上 這 些 副 程 式 並 不 會 同 時 被 呼 叫 到 , 也 就 是 說 , 程 式 並 不 會 同 時 使 用 到 所 有 的 變 數 , 這 無 形 中 形 成 了 記 憶 體 的 浪 費 , 所 以 系 統 才 會 改 採 執 行 到 某 一 程 式 區 塊 , 才 為 該 區 塊 的 變 數 配 置 記 憶 體 的 策 略 。 使 用 區 域 變 數 要 特 別 注 意 變 數 的 「 初 值 」 , 由 於 程 式 每 次 執 行 過 區 塊 時 , 變 數 的 記 憶 體 就 會 被 釋 放 出 來 , 所 以 它 無 法 保 留 住 前 一 次 的 值 , 因 此 程 式 每 次 進 入 區 塊 , 變 數 的 值 都 必 須 重 新 決 定 , 如 何 決 定 在 不 同 的 程 式 語 言 中 有 不 同 的 方 式 , 以 QBASIC 為 例 , 會 把 區 域 變 數 初 設 成 0, 但 C 語 言 就 不 會 這 麼 做 , 如 果 您 在 C 程 式 中 不 為 區 域 變 數 設 定 初 值 , 區 域 變 數 的 值 就 等 於 系 統 為 它 所 配 置 出 來 的 記 憶 體 當 時 的 值 , 這 個 值 可 能 是 先 前 其 他 變 數 所 留 下 來 的 垃 圾 。 |
區 域 變 數 的 值 不 是 每 次 被 初 設 成 0, 就 是 前 一 次 記 憶
體 所 殘 留 下 來 的 值 , 如 果 說 我 們 想 保 留 變 數 在 最 後 一 次
運 算 時 所 留 下 來 的 值 , 那 麼 該 怎 麼 做 呢 ? 最 常 見 的 方 式
就 是 在 變 數 宣 告 時 加 上 static, 也 就 是 宣 告 一 個 靜 態 的 區
域 變 數 。
靜 態 區 域 變 數 的 活 動 範 圍 與 區 域 變 數 完 全 相 同 , 但 生 命 週 期 卻 與 全 域 變 數 相 同 , 當 程 式 載 入 系 統 時 , 它 的 記 憶 體 就 被 配 置 出 來 了 , 直 到 程 式 結 束 時 , 記 憶 體 才 被 回 收 , 因 此 程 式 執 行 過 靜 態 區 域 變 數 所 屬 的 區 塊 時 , 靜 態 區 域 變 數 還 能 夠 保 留 原 來 的 值 , 舉 個 C 語 言 的 例 子 : FuncX() { static int x = 10; ... x = x + 1; } 當 程 式 第 一 次 執 行 過 FuncX() 之 後 , 靜 態 區 域 變 數 x 的 值 仍 會 維 持 著 11 。 另 外 請 注 意 一 件 事 , 如 果 程 式 語 言 允 許 我 們 在 宣 告 時 為 靜 態 區 域 變 數 設 定 初 值 , 如 例 子 中 的 「 static int x = 10;」 , 這 個 設 定 靜 態 區 域 變 數 值 的 動 作 只 會 執 行 一 次 , 也 就 是 在 靜 態 變 數 被 配 置 出 來 的 時 候 , 因 此 程 式 每 次 進 入 副 程 式 時 並 不 會 把 x 的 值 設 定 成 10, 而 是 始 終 保 留 前 一 個 值 。 |