形式引數

在程式設計中，**參數**（parameter）又稱**形式引數**（formal argument），是一種在呼叫子程式時用以向子程式傳遞資料的特殊變數，這些被傳遞資料也就是子程式**引數**（arguments）的值。參數的有序列表通常包含在次常式的定義中，因此在每次呼叫次常式時也會計算這些傳入的引數，並且將對應資料送到子程式中。

不同於在數學中通常所使用的引數，計算機科學中的引數是在引動過程（invocation）或者呼叫語句（call statement）中傳遞給函式（function）、程式（procedure）或者常式（routine）的實際輸入表達式，而參數是子程式實現內部的變數。例如，定義一個 `add` 子程式為 `def add(x, y): return x + y` 那麼 `x,y` 就是一對參數。當呼叫這個子程式時，例如 `add(2, 3)` 那麼，`2,3` 就是一對引數，請注意，呼叫上下文中的變數（及其表達式）可以是引數，如果以子程式的形式呼叫 `a = 2; b = 3; add(a, b)` 則變數 `a, b` 是引數，而不是值 `2, 3`。

在最常見的傳值調用情況下，參數會在子程式中充當新的局部變數，並初始化為引數的值（如果引數是變數，則為引數的局部（隔離）副本）。但在例如傳參照調用的其他情況下，調用者提供的引數變數可能會受到被調用次常式中操作的影響。

所使用的語言一般定義了如何聲明參數以及將引數（的值）傳遞給次常式的參數的語義，但是在任何特定計算機系統中如何表示該參數的細節取決於該系統的調用約定。

## 範例

以下使用 C 語言編寫的程式定義了一個名為 `SalesTax` 的函式，並具有一個名為 `price` 的參數。`price` 的類型是 `double`，也就是雙精度浮點數。該函式的返回類型也是 `double`。

定義函式後，可以按以下方式調用它：

範例中使用了引數 `10.00` 調用該函式，此時值 `10.00` 就會分配給 `price`，然後該函式開始計算其結果。產生結果的步驟在下面指定，並用 `{}` 括起來。`0.05 * price` 表示要做的第一件事是將價格乘以 `0.05`，即得出 `0.50`。`return` 表示函式將產生的結果 `0.05 * price` 返回。因此，最終結果（忽略將小數部分表示為二進制時可能遇到的捨入誤差）為 `0.50`。

## 參數和引數

術語**參數**和**引數**在不同的程式語言中可能會具有不同的含義。有時它們可以互換使用，並且以上下文區分含義。術語參數（也稱為形式參數（形參，formal parameter））通常用來指該變數作為在函式定義中變數的內容。而引數（也稱為實際參數（實參，actual parameter））是指在函式調用中提供的實際輸入。例如，如果將一個函式定義為 `def f(x): ...`，`x` 則為參數。若定義有 `a = ...; f(a)` 來呼叫函式，那麼這裡的 `a` 就是引數。

參數是一個（未綁定實際值的）變數，而引數可以是一個值或變數，或者是涉及值和變數的更複雜的表達式。在由值呼叫的情況下，所傳遞給函式是自變數的值。例如 `f(2)` 和 `a = 2; f(a)` 對函式的呼叫是相同的，在以變數作為參數通過參照調用時，若函式調用的語法可以保持不變，則傳遞的是對該變數的參照。具體 [[求值策略 #傳值呼叫（Call by value）| 按值傳遞]] 或者是 [[求值策略 #傳參照呼叫（Call by reference）| 按參照傳遞]] 的細節規範則由函式的聲明以及定義中進行。

一般而言，參數出現在過程定義中，引數則出現在過程調用中。在函式 `f(x) = x*x` 的定義中，變數 x 是參數。在函式 `f(2)` 調用中，值 2 是該函式的引數。簡單而不太準確的概擴來說參數是類型，而引數是實作。

參數是過程的原生屬性，包含在其定義中。例如，在許多語言中，將兩個提供的整數加在一起併計算總和的過程將需要兩個參數，每個整數一個。通常，可以使用任意數量的參數或完全不使用參數來定義過程。如果過程具有參數，則其定義中指定參數的部分稱為其參數列表（parameter list）。 

相反，引數是調用過程時提供給過程的表達式，其通常是一個與參數之一匹配的表達式。與參數（它們構成過程定義的不變部分）不同，自變數在調用之間可能有所不同。每次調用過程時，過程調用中指定引數的部分稱為引數列表（argument list）。 

儘管參數通常也被混稱為引數，但是在運行時調用次常式時，有時會將引數視為分配給參數變數的實際值或參照。在討論正在調用次常式的程式碼時，傳遞給次常式的任何值或參照都是引數。

在討論次常式定義中的程式碼時，次常式的參數列表中的變數是參數，而運行時參數的值則會是引數。例如，在 C 語言中，處理執行緒時通常會傳入 `void *` 類型的參數並將其強制轉換為預期的類型： 

為了更好地理解它們之間的區別，請考慮以下用 C 語言編寫的函式： 

函式 `Sum` 具有兩個參數，分別名為 `addend1` 和 `addend2`，這個函式將傳遞給參數的值相加，然後將結果返回給次常式的調用方（使用 C 語言編譯器自動提供的技術）。 
調用 `Sum` 函式的代碼如下所示： 

變數 `value1` 和 `value2` 用值初始化。在這種情況下，`value1` 和 `value2` 都是 `sum` 函式的引數。

在運行時，分配給這些變數的值作為參數傳遞給函式 `Sum`。在 `Sum` 函式中，將會對參數 `addend1` 和 `addend2` 進行求值，分別得出參數 40 和 2。將添加參數的值，並將結果返回給調用方，在此將其分配給變數 `sum_value`。 

由於參數和引數之間的差異，有可能向過程提供不合適的引數，也可能提供過多或過少的引數，要麼一個或多個引數可能是錯誤的類型，或者是以錯誤的順序提供了引數。這些情況中的任何一種都會導致參數列表和引數列表之間不匹配，並且該過程通常會返回意外的答案或生成執行期錯誤。

## 數據類型

在強類型程式語言中，必須在過程聲明中指定每個參數的類型。使用類型推斷的語言會嘗試從函式的主體和用法中自動推斷類型。動態類型的程式語言則會將類型解析推遲到運行時。弱類型語言幾乎沒有類型解析，甚至依靠程式員來確保正確性。

一些語言使用特殊的關鍵字（例如 `void`）來指示該次常式沒有參數。在形式類型理論中，此類函式採用空參數列表（其類型不是 `void`，而是 `unit`）。

## 引數傳遞

將參數分配給參數的確切機制稱為參數傳遞（argument passing），該機製取決於可以使用關鍵字來指定的參數求值策略（通常為按值傳遞）。

### 默認引數

某些程式語言（例如 Ada，C++，Clojure，Common Lisp，Fortran 90，Python，Ruby，Tcl 和 Windows PowerShell）允許在次常式的聲明中顯式或隱式給出默認引數。這允許調用者在調用次常式時忽略該參數。如果顯式給出了默認引數，則如果調用者未提供該值，則使用該值。如果默認引數是隱式的（有時通過使用諸如 `Optional` 之類的關鍵字），則該語言將提供一個通常值（例如 `null`、`Empty`、0、空字串等）。

PowerShell 範例：

默認引數可以看作是可變長度引數列表的一種特殊情況。

### 可變長度參數列表

某些語言允許將次常式定義為接受可變數量的引數。對於此類語言，次常式必須遍歷參數列表。

PowerShell 範例：

### 命名參數

某些程式語言（例如 Ada 和 Windows PowerShell）許次常式具有命名參數。這使調用代碼更具自記錄性。它還為調用者提供了更大的靈活性，通常允許更改參數的順序，或者根據需要省略參數。

PowerShell 範例：

### 函式語言中的多個參數

在 lambda 演算中，每個函式只有一個參數。通常認為具有多個參數的函式在 lambda 演算中表示為具有第一個引數的函式，並返回具有其餘引數的函式。這種轉換也被稱為柯里化（Currying）。一些程式語言（例如 ML 和 Haskell）遵循此方案。在這些語言中，每個函式都只有一個參數，看起來像多個參數的函式的定義，實際上是返回一個函式的函式的定義的語法糖等。在這些語言以及 lambda 演算中的函式應用是左關聯的，因此，這種操作看起來會像是將函式應用於多個引數的函式先將函式應用於第一個引數，然後將結果函式應用於第二個自變數，並依此類推。

## 輸出參數

**輸出參數**，也稱為**出參**或**返回參數**，是一種用於輸出而不是更通常的使用用於輸入的參數。在某些語言中，尤其是 C 和 C ++，輸出參數是一種習慣用法，而其他語言則內建了對輸出參數的支援。內建支援輸出參數的語言包括 Ada、Fortran、SQL 的各種拓展程式（例如：PL/SQL 和 Transact-SQL）、Ｃ# 和.Net Framework、Swift 以及手稿語言 TScript。

更準確地說，**參數模式**或**參數**共有三種類型：輸入參數（入參）、輸出參數（出參）、輸入輸出參數（出入參），這些參數往往使用 `in`、`out`、`inout` 來進行表示。輸入引數輸入引數（針對輸入參數的引數）必須是一個值，例如已經被初始化了的變數或常值，而且不能重新定義獲分配它。輸出引數必須是可分配變數，但不必初始化而且任何現有值均不可存取輸入值，執行函式後必須分配一個值。輸入輸出引數必須是已初始化了的可分配變數，並且可以選擇給其分配一個值。確切的要求和執行情況因語言而異，例如在 Ada 83 中，即使分配後也只能分配輸出參數（在 Ada 95 中刪除了該參數，以消除對輔助累加器變數的需要）。這有些類似於值的概念，也就是左值（l-value，具有值）、右值（r-value，可分配）以及左右值（l-value/r-value，具有值且可分配），不過這些術語在 C 語言中有特殊含意。

在某些情況下，只有輸入和輸入輸出是有區別的，而輸出則被視為輸入 / 輸出的特定用途，而在其他情況下，僅支援輸入和輸出（但不支援輸入輸出）。默認模式因語言而異：在 Fortran 90 中，默認輸入 / 輸出，在 C＃和 SQL 擴展中，默認輸入 / 輸出，在 TScript 中，每個參數都明確指定為輸入或輸出。

從語法上講，參數模式通常在函式聲明（例如在 C＃中：`void f(out int x)`）中用關鍵字表示。通常，輸出參數通常放在參數列表的末尾以清楚地區分它們。TScript 使用另一種方​​法，其中在函式聲明中列出了輸入參數，然後列出了輸出參數，並用冒號（`:`）分隔，並且函式本身沒有返回類型，如在此函式中一樣，該函式用於計算文字的大小分段：

參數模式是指稱語義的一種形式，其表明了程式設計師的意圖並允許編譯器捕獲錯誤來應用優化，它們不​​一定暗示操作語義（參數傳遞的實際發生方式）。值得注意的是，雖然輸入參數可以通過值調用實現，而輸出和輸入輸出參數可以通過參照調用實現，這是在沒有內建支援的情況下以語言實現這些模式的直接方法，但也並非總是如此實施。《Ada '83 基本原理（英語：Ada '83 Rationale）》中詳細討論了這種區別，其中強調了參數模式是從中實際實現的參數傳遞機制（通過參照或通過複製）抽像出來的。例如，在 C＃中，按值傳遞輸入參數（默認，無關鍵字），而按參照傳遞輸出和輸入 / 輸出參數（`out` 和 `ref`），而在 PL / SQL 中傳遞輸入參數（`IN`）通過參照，默認情況下通過值傳遞輸出和輸入 / 輸出參數（`OUT` 和 `IN OUT`），並將結果復制回去，但是可以使用 `NOCOPY` 編譯器提示通過參照傳遞。
與輸出參數在語法上相似的構造是將返回值分配給與函式名稱相同的變數。 可以在 Pascal 和 Fortran 66 和 Fortran 77 中發現，此處使用 Pascal 範例：

這在語義上是不同的，因為在調用時僅對函式進行求值–不會從調用範圍傳遞變數來儲存輸出。

### 使用

輸出參數的主要用途是從一個函式返回多個值，而輸入 / 輸出參數的用途是使用參數傳遞（而不是像全域變數那樣在共享環境中）來修改狀態。 返回多個值的一個重要用途是解決返回值和錯誤狀態的半謂詞問題。

例如，要從 C 函式中返回兩個變數，可以寫：

其中 `x` 是輸入參數， `width` 和 `height` 是輸出參數。
C 和相關語言的一個常見用例是異常處理 ，其中函式將返回值放在輸出變數中，並返回一個布爾值，該布爾值對應於函式是否成功。 一個典型的例子是.NET 中的 `TryParse` 方法，尤其是 C＃，它將字串解析為整數，成功則返回 `true` ，失敗則返回 `false` 。 其具有以下形式：

可以這樣使用：

類似的考慮適用於返回幾種可能類型之一的值，其中返回值可以指定類型，然後將值儲存在幾種輸出變數之一中。

### 缺點

在現代編程中，通常不鼓勵使用輸出參數，因為它們笨拙、令人困惑且級別太低，普通的返回值相當容易理解和使用。值得注意的是，輸出參數涉及具有副作用（修改輸出參數）的函式，並且在語義上與參照相似，比純函式和值更容易混淆，並且輸出參數與輸入 / 輸出參數之間的區別可能微妙。 此外，由於在通常的編程樣式中，大多數參數只是輸入參數，因此輸出參數和輸入輸出參數是異常的，從而容易引起誤解。

輸出和輸入輸出參數會阻止函式組合，因為輸出儲存在變數中，而不是表達式的值中。 因此，必須首先聲明一個變數，然後函式鏈的每一步都必須是一個單獨的語句。 例如，在 C ++ 中，以下函式組成：

當寫有輸出和輸入輸出參數時，可以寫為（對於 `F` 是輸出參數，對於 `G` 是輸入 / 輸出參數）：

在具有單個輸出或輸入 / 輸出參數且沒有返回值的函式的特殊情況下，如果該函式還返回輸出或輸入輸出參數（或在 C / C ++ 中是其位址），則可以構成函式，在這種情況下，以上內容將變為：

### 替代方式

輸出參數的用例有多種選擇。

為了從一個函式返回多個值，一種替代方法是返回一個元組 。 從語法上講，如果可以使用自動序列拆包和並行賦值 （例如在 Go 或 Python 中），這會更清楚：

為了返回幾種類型之一的值，可以使用標籤聯合 。最常見的情況是可空類型 （ 可選型別 ），其中返回值可以為 `null` 以指示失敗。對於異常處理，可以返回可為 `null` 的類型，也可以引發異常。 例如，在 Python 中，可能有以下兩種情況之一：

或者，更慣用的方法：

不需要局部變數並在使用輸出變數時復制返回值的微優化也可以通過足夠複雜的編譯器應用於常規函式和返回值。
使用 C 和相關語言輸出參數的通常替代方法是返回包含所有返回值的單個數據結構。 例如，給定一個封裝寬度和高度的結構，可以這樣寫：

在面向物件的語言中，通常不使用輸入輸出參數，而是通過 [[求值策略 #傳共享物件呼叫（Call by sharing）| 傳共享調用]] ，將參照傳遞給物件然後對物件進行突變來使用調用 ，而不更改變數所參照的物件。

## 相關條目

* 求值策略
* 運算子多載
* 自由變數和約束變數

## 參考資料