複製鏈接
請複製以下鏈接發送給好友

可變參數函數

鎖定
在計算機程序設計,一個可變參數函數是指一個函數擁有不定引數,即是它接受一個可變數目的參數。簡單來説,就是函數的參數個數可變,參數類型不定的函數。
不同的編程語言對可變參數函數的支持有很大差異。
中文名
可變參數函數
外文名
Variable parameter function
定    義
一個擁有不定引數的函數
學    科
計算機科學

可變參數函數簡介

在計算機編程時,可變參數函數中參數是一個可變數目,即這個函數擁有不定引數。
一般而言,在設計函數時會遇到許多數學邏輯操作,是需要一些可變功能。例如,計算數字串的總和、字符串的聯接或其他操作過程,都可以存在任意數量的參數。
另一種許多語言都實現為可變參數函數的是格式輸出函數,在C語言的printf函數和Common Lisp的format函數就是例子。這些函數都需要一個參數,指定格式的輸出,再讀取可變參數的值進行格式化 [1] 
另外,可變參數函數在某些語言存在安全問題。例如C語言在沒有長度檢查和類型檢查,在傳入過少的參數或不符的類型時可能會出現溢位的情況,更可能會被利用為攻擊目標。所以,在設計函數時可以先考慮其他替補方案,例如以類型安全的方式——重載

可變參數函數C/C++中的舉例

在C語言中 [2]  ,C標準函式庫的stdarg.h標頭檔定義了提供可變參數函數使用的。在C++,應該使用標頭檔cstdarg。
要創建一個可變參數函數,必須把省略號(...)放到參數列表後面。函數內部必須定義一個va_list變數。然後使用va_start、va_arg和va_end來讀取。例如:
#include <stdio.h>
#include <stdarg.h>
double average(int count, ...); /* 函數聲明,計算參數的平均值。直到參數為0時停止計算 */
int main(void) /* 測試代碼 */
{        double avg;        avg = average(3, 2, 1, 5, 0);        printf("%f\n", avg);        return 0;
}
double average(int count, ...)
{        va_list ap;        int i, cnt = 0;  /* cnt 表示參數個數 */        double tot = 0;  /* 參數的和 */        va_start(ap, count);        for (i = count; i; i = va_arg(ap, int), cnt++) /* i為當前獲取參數的值 */                tot += i;        va_end(ap);  /* 將參數列表清空 */        return tot / cnt;
}
這個是一段計算平均數的程式碼,可以輸入任意數量的小數並計算平均數,注意計算以0停止計算。請注意,函數不知道參數的數量或它們的類型,這裏要求的類型是double,而且第一個參數傳遞可變參數的數量。在另外的情況下,例如printf,參數的數量和類型都設定在格式字符串中。在這兩種情況下,程序員實際上需要提供正確的參數,如果參數傳遞少了或參數的類型不正確,導致讀入內存的無效區(溢位),這樣會有安全漏洞如格式字符串攻擊。
C++中可變參數的函數是從C中繼承而來,可變參數的函數是指函數的參數個數可變,參數類型不定的函數。我們最常見的就是printf()。

可變參數函數可變參數函數實現原理

指定參數的函數實現很簡單,通過通過指定的參數名訪問就行了。但是如果不指定的呢?函數的調用的參數會進行壓棧處理。而對參數的壓棧是從右到左進行壓棧。而參數和參數之間存放是連續的,也就是説,只要知道第一個參數的地址和類型,以及其他參數的類型,就可以獲取各個參數的地址。
比如:
int printf(const char* format,...)

printf("%d and %c,a,b");
函數調用內存結構如右:
這裏的a是int型,b是char型。printf()有3個參數,一個const char*,一個是int,一個是char。所以參數壓棧的順序就是先將b入棧,再將a入棧,最後是format入棧,由於是向下(低地址)生長的,所以在知道了format的地址之後,所有的參數地址都可以計算出來。

可變參數函數聲明和定義

可變參數函數的聲明很簡單,對於不定參數部分用“...”表示即可。但是實現原理可以看到,第一個的參數的地址是必須提供的,也就是可變參數必須至少包含一個參數,這個參數用來尋址,實現對所有參數的訪問。
當然通常也會在對第一個參數進行一些特殊處理以方便函數的實現,比如強制指定為參數個數,或者像printf一樣使用格式佔位符來。

可變參數函數C++11/C++14中的舉例

C++11新增了initializer_list物件,當編譯器遇到大括號陣列變成函數的參數時,會自動將大括號陣列轉型成initializer_list物件,因此可以使用此原理來做出“相同型別”參數的可變參數函數。
#include <iostream>
using namespace std;
template<typename T>
T sum(initializer_list<T> il) {    T data(0);    for (T i : il)        data += i;    return data;
}
int main() {    cout << sum( { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }) << endl;    return 0;
}

可變參數函數JavaScript中的舉例

在JavaScript(簡稱js)中,函數的可變參數用法很巧妙 [3] 
function test(){ // 不管函數是否聲明參數,所有的參數都會添加到arguments中,arguments以數組的形式將參數保存起來 var params = arguments; // 以數組的形式輸出 console.log(params);
};
test("a");// 結果:["a"]
test("a", 1);// 結果:["a", 1]
test("a", 1, function(){});// 結果:["a", 1, function (){}]
參考資料
  • 1.    陳維興.C++面向對象程序設計:普通高等教育"十一五"國家級規劃教材,2010年10月
  • 2.    丹尼斯·裏奇, 布萊恩·柯林漢.C程序設計語言:不詳,1978
  • 3.    尼古拉斯·C·薩卡斯.JavaScript高級程序設計:清華大學出版社,2005 年