可變引數函式使用

2021-05-23 02:58:21 字數 3950 閱讀 7793

va函式的定義和va巨集

c語言支援

va函式,作為

c語言的擴充套件

--c++

同樣支援

va函式,但在

c++中並不推薦使用,

c++引入的多型性同樣可以實現引數個數可變的函式。不過,

c++的過載功能畢竟只能是有限多個可以預見的引數個數。比較而言,c中的

va函式則可以定義無窮多個相當於

c++的過載函式,這方面

c++是無能為力的。

va函式的優勢表現在使用的方便性和易用性上,可以使**更簡潔。

c編譯器為了統一在不同的硬體架構硬

件平台上的實現,和增加**的可移植性,提供了一系列巨集來遮蔽硬體環境不同帶來的差異。

ansi c

標準下,

va的巨集定義在

stdarg.h

中,它們有:va_listva_start()va_arg()va_end()

引數可以分為兩部分:個數確定的固定引數和個數可變的可選引數。函式至少需要乙個固定引數,固定引數的宣告和普通函式一樣;可選引數由於個數不確定,宣告時用

"…"表示。固定引數和可選引數公同構成乙個函式的引數列表。

各個巨集函定義

#define _intsizeof(n)  ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

#define va_start(ap,v)  ( ap = (va_list)&v + _intsizeof(v) )

#define va_arg(ap,t)    ( *(t *)((ap += _intsizeof(t)) - _intsizeof(t)) )

作用:

va_list arg_ptr

:定義乙個指向個數可變的引數列表指標;

va_arg(arg_ptr, type)

:返回引數列表中指標

arg_ptr

所指的引數,返回型別為

type

,並使指標

arg_ptr

指向引數列表中下乙個引數。

va_copy(dest, src)

:dest

,src

的型別都是

va_list

,va_copy()

用於複製引數列表指標,將

dest

初始化為

src。

va_end(arg_ptr)

:清空引數列表,並置引數指標

arg_ptr

無效。

說明:指標

arg_ptr

被置無效後,可以通過呼叫

va_start()

、va_copy()

恢復arg_ptr

。每次呼叫

va_st

art() / va_copy()

後,必須得有相應的

va_end()

與之匹配。引數指標可以在引數列表中隨意地來回移動,但必須在

va_start() …

小結: 1

、標準c

庫的中的三個巨集的作用只是用來確定可變引數列表中每個引數的記憶體位址,編譯器是不知道引數的實際數目的。

2、在實際應用的**中,程式設計師必須自己考慮確定引數數目的辦法,如

⑴在固定引數中設標誌——

printf

函式就是用這個辦法。後面也有例子。

⑵在預先設定乙個特殊的結束標記,就是說多輸入乙個可變引數,呼叫時要將最後乙個可變引數的值設定成這個特殊的值,在函式體中根據這個值判斷是否達到引數的結尾。無論採用哪種辦法,程式設計師都應該在文件中告訴呼叫者自己的約定。

3、實現可變引數的要點就是想辦法取得每個引數的位址,取得位址的辦法由以下幾個因素決定:

①函式棧的生長方向

②引數的入棧順序 ③

cpu的對齊方式

④記憶體位址的表達方式

4、取得位址後,再結合引數的型別,程式設計師就可以正確的處理引數了。理解了以上要點,相信稍有經驗的讀者就可以寫出適合於自己機器的實現來。

void func(int a, ..., int c)

所有型別固定的引數都必須出現在引數列表的開始。這樣根據前面的論述,我們就可以得到所有型別固定的引數。

在設計具有不定引數列表的函式的時候,我們有兩種方法來確定到底多少引數會被傳遞進來。方法1

是在型別固定的引數中指明後面有多少個引數以及他們的型別。

printf

就是採用的這種方法,它的

format

引數指明後面每個引數的型別。方法2

是指定乙個結束引數。這種情況一般是不定引數擁有同樣的型別,我們可以指定乙個特定的值來表示引數列表結束。下面這個

sum函式就是乙個例子:

int sumi(int c, ...)

...

return sum;

} 使用這個函式的**為:

int main(int argc, char* argv)

...

使用不定引數列表,有兩個問題特別需要注意。問題1

的理解相對簡單:我們在過載乙個函式的時候,不能依賴不定引數列表部分對函式進行區分。

假定我們定義兩個過載函式如下:

int func(int a, int b, ...)

int func(int a, int b, float c);

則上述函式會導致編譯器不知道怎麼去解釋

func(1,2, 3.3)

,因為當第三個引數為浮點數時,兩個實現都可以滿足匹配要求。一般情況,個人建議對於不定引數函式不要去做過載。問題2

關於型別問題。絕大多數情況下,c和

c++的變數都是強型別的,而不定引數列表屬於乙個特例。

當我們呼叫

va_arg

的時候,我們指明下乙個引數的型別,而在執行的時候,

va_arg

正是根據這個資訊在堆疊上來找到對應的引數的。如果我們需要的型別和真實傳遞進來的引數完全一致時自然沒有問題,但是假如型別不一樣,則會有**煩。

假如上面的的

sumi

函式,我們用下面方法呼叫:

int sum = sumi(1, 2.2, 3, 0)

注意第二個引數我們傳入了乙個

double

型別的2.2

,我們希望

sumi

在做加法時可以做隱式型別轉換,轉換為

int進行計算。但是實際情況時,當我們分析到這個引數時,呼叫的是:

c=va_arg(ap,int)

根據前文

va_arg

的定義,這個巨集被翻譯成:

#define va_arg(ap,t)    ( *(int *)((ap += _intsizeof(int)) - _intsizeof(int)) )

如果後面的

+=計算出正確的位址,最後就變成

*(int*)addr

如果希望能得到正確的整數值,必須要求

addr

所在的位址是乙個真實的

int型別。但是當我們傳入

double

時,實際上其記憶體布局和

int完全不同,因此我們得不到需要的整數。感興趣的朋友可以用下面簡單的**做測試:

double a;

a=1.1;

int b = *(int*) & a;

因此,當我們呼叫有不定引數列表的函式時,不要期望系統做隱式型別轉換,系統不會做這種檢查或者轉換,你給的引數型別必須嚴格和你希望的值一樣。

可變引數函式

c函式要在程式中用到以下這些巨集 void va start va list arg ptr,prev param type va arg va list arg ptr,type void va end va list arg ptr va list 用來儲存巨集va start va arg和v...

可變引數函式

一 什麼是可變引數 我們在c語言程式設計中有時會遇到一些引數個數可變的函式,例如printf 函式,其函式原型為 int printf const char format,它除了有乙個引數format固定以外,後面跟的引數的個數和型別是可變的 用三個點 做引數佔位符 實際呼叫時可以有以下的形式 pr...

可變引數函式

stdarg.h是 c語言中c標準函式庫的 標頭檔案,stdarg是由standard 標準 arguments 引數 簡化而來,主要目的為讓函式能夠接收可變引數。stdarg.h 資料型別 型別名稱 描述 va list 用來儲存巨集va arg與巨集va end所需資訊 巨集名稱 描述va st...