跳轉至內容

程式語言導論/引數匹配

來自華夏公益教科書,開放的書籍,開放的世界

幾乎所有程式語言都為開發者提供了構建可從程式不同部分啟用的小程式碼片段的能力。這種抽象在不同的語言中有著不同的名稱。舉幾個例子,Haskell 提供函式。C 除了函式之外,還提供過程,過程是不返回值的函式。Java 和 C# 提供方法。Prolog 提供推理規則。為簡單起見,本章將所有這些抽象稱為函式。在任何情況下,呼叫程式碼都必須向被呼叫程式碼傳遞引數。傳遞這些引數的方式有很多,本章將討論這些策略。

引數匹配

[編輯 | 編輯原始碼]

許多作者將函式宣告中使用的引數稱為形式引數。例如,下面的 Python 函式 div 有兩個形式引數,被除數和除數

def div(dividend, divisor):
  r = dividend / divisor
  print "Result = ", r

當函式被呼叫時,我們向它傳遞所謂的實際引數。例如,在下面的程式碼片段中,我們向函式 div 傳遞了兩個引數,變數 x 和常數 2.0

>>> x = 3.0
>>> print div(x, 2.0)
Result =  1.5

在這個例子中,儲存在變數 x 中的值,也就是實際引數,用於初始化形式引數被除數的值。在這種情況下,形式引數和實際引數之間的匹配是位置匹配。換句話說,實際引數是根據它們在函式呼叫中出現的順序與形式引數匹配的。雖然位置方法是匹配形式引數和實際引數的常用方法,但還有一種方法:名義匹配。

>>> x = 3.0
>>> div(divisor=2.0, dividend=x)
Result =  1.5

這次,我們明確地命名了與形式引數匹配的實際引數。名義匹配存在於少數程式語言中,包括 ADA。

帶預設值的引數

[編輯 | 編輯原始碼]

一些程式語言允許開發者為引數分配預設值。例如,讓我們考慮之前看到的相同 Python 函式 div,但這次實現略有不同

def div(dividend=1.0, divisor=1.0):
   r = dividend / divisor
   print "Result = ", r

在這個例子中,每個形式引數都被分配了一個預設值。如果與該形式引數對應的實際引數不存在,則將使用預設值。以下所有呼叫都是可能的

>>> div()
Result =  1.0
>>> div(dividend=3.2, divisor=2.3)
Result =  1.39130434783
>>> div(3.0, 1.5)
Result =  2.0
>>> div(3.0)
Result =  3.0
>>> div(1.5, divisor=3.0)
Result =  0.5

有很多語言使用預設值。C++ 是這個家族中一個著名的例子。例如,以下所有呼叫都會返回有效答案。如果實際引數的數量少於形式引數的數量,則使用預設值。在這種情況下,匹配是從最左邊的宣告向最右邊的宣告進行的。因此,預設值將應用於從最右邊的宣告到最左邊的形式引數。

#include <iostream>
class Mult {
  public:
    int f(int a = 1, int b = 2, int c = 3) const {
      return a * b * c;
    }
};
int main(int argc, char** argv) {
  Mult m;
  std::cout << m.f(4, 5, 6) << std::endl;
  std::cout << m.f(4, 5) << std::endl;
  std::cout << m.f(5) << std::endl;
  std::cout << m.f() << std::endl;
}

帶可變數量引數的函式

[編輯 | 編輯原始碼]

有一些程式語言允許開發者建立帶可變數量引數的函式。最著名的例子是 C,C 中的典型例子是 printf 函式。該過程接收一個描述將要產生的輸出的字串作為輸入。透過分析這個字串,printf 函式的實現“知道”它可能接下來會接收哪些引數。可能是,因為 C 編譯器中沒有任何東西強制開發者傳遞正確數量的引數並使用正確的型別。例如,下面的程式很可能會由於段錯誤導致執行時錯誤。

#include "stdio.h"
int main(int argc, char **argv) {
  printf("%s\n", argc);
}

C 語言為開發者提供了一種特殊的語法,以及一些庫函式,使他們能夠建立帶可變數量引數的函式。例如,下面的函式可以將四個或更少的整數相加。第一個引數應該是函式期望的引數數量。變數 ap 是一個指向每個引數的資料結構,如傳遞給函式 foo 的引數。在迴圈內部,我們使用宏 va_arg 獲取指向每個引數的指標。這些指標儲存在陣列 arr 中。注意,va_arg 是 C 中模擬引數多型性的一個例子。該語言沒有型別構造器,也就是說,不能將型別傳遞給函式。但是,我們可以透過宏來模擬這種能力。

#include <stdio.h>
#include <stdarg.h>

int foo(size_t nargs, ...) {
  int a = 0, b = 0, c = 0, d = 0;
  int *arr[4];
  va_list ap;
  size_t i;
  arr[0] = &a;
  arr[1] = &b;
  arr[2] = &c;
  arr[3] = &d;
  va_start(ap, nargs);
  for(i = 0; i < nargs; i++) {
    *arr[i] = va_arg(ap, int);
  }
  va_end(ap);
  return a + b + c + d;
}

int main() {
  printf("%d\n", foo(0));
  printf("%d\n", foo(1, 2));
  printf("%d\n", foo(2, 2, 3));
  printf("%d\n", foo(3, 2, 3, 5));
}

同樣,沒有保證會向 foo 傳遞正確數量的引數。例如,下面的程式將列印值 10。這種行為的答案在於變數 u 的二進位制表示。該變數在記憶體中佔用 8 個位元組,每半個位元組被視為一個引數。

int main() {
  // 10,00000000000000000000000000000011
  unsigned long long int u = 8589934595;
  printf("%d\n", foo(2, 5, u));
}

評估策略

華夏公益教科書