c++函數如何接受數量不定的函數參數?
template&
void func(TArgs... args) {
auto arguments = std::forward_as_tuple(std::forward&
std::get&<0&>(arguments); //參數0
std::get&<1&>(arguments); //參數1
//...依此類推
}
不過我知道這肯定不是你想要的答案
如果你需要的是像printf那樣的玩意,那麼你應當使用varg那套東西,大致的套路如下:
- 在函數的參數列表的最後,以...作為最後一個參數,例如int my_func(int a, float b, int c, ...)
- 在函數實現里,用va_start、va_arg、va_end這些東西來獲取那些不定的參數。
詳見stdarg.h的文檔。
這種可變參數的函數,是在編譯時由編譯器在函數調用處決定壓棧參數的個數,而函數實現在運行時經由某個參數獲取參數的個數。比如printf,參數的個數隱含在格式模板里。
如果你可以接受每個參數長度都產生一個單獨的函數實例,那就參照另一個答案。
常用的兩種辦法:
- std::va_list - cppreference.com
- Parameter pack - cppreference.com
Ellipsis
7.14 — Ellipsis (and why to avoid them)
知識點:Parameter pack - cppreference.com
template&
void tprintf(const char* format, T value, Targs... Fargs) // recursive variadic function
{
for ( ; *format != " "; format++ ) {
if ( *format == "%" ) {
std::cout &<&< value;
tprintf(format+1, Fargs...); // recursive call
return;
}
std::cout &<&< *format;
}
}
這個問題呢,可能你想問的是 C 語言里的那個 ... 的語法相關的問題。有一個 C 語言的頭文件里有三個相關的宏,幫你定義幾個變數,取棧上的地址,並在使用後清空為 NULL。這幾個宏看下定義弄明白了其實原理比較簡單也沒什麼可細講的,那麼要求:
(1)函數需要是 C 調用約定(也是默認的調用約定),也就是調用方負責清理 stack (釋放參數),因為被調用者不知道調用方在棧上 push 了多少東西,所以它無法平衡 stack。
(2)需要至少有一個參數作為基準,以它的地址為基準,在棧頂定位到後續的變數。比如說 printf 這個函數,就是以第一個參數為基準,在解析 _Format 字元串的過程中,從棧上逐個獲取變數。
同時我也想寫一個函數 A(比如輸出日誌),就是在這個函數中接受寫數量不定的參數,然後在這個函數中,再調用例如 printf 這樣的函數 B,把這些參樣傳遞過去,但是我發現,這樣就相當於需要把 A 的棧上參數原封不動的 copy 一份到棧頂,在調用函數 B。這樣我必須知道我要 COPY 多少位元組,那就意味著我也同樣需要解析 _Format 字元串,才能確定棧上有多少東西,然而這個解析如果考慮 _format 的所有支持特性,將是很繁瑣的,不亞於重寫函數 B 一次。當然,還有個辦法,是把需要考慮的尺寸,從調用方傳遞進來,有點麻煩,但是可以省卻解析 _Format 的過程。例如:
#include &
//按照 4 bytes 對齊。
#define ALIGN_4(_size) ((_size)+3(-4))
//要求運行環境:WIN32,棧按照 4 bytes 對齊,
//sizeof size_t|int = 4 bytes。
char* WriteLog(char* buf, size_t bufSize,
const char* _Format, size_t argSize, ...) {
//簡稱 WriteLog 為函數 A,sprintf_s 為函數B。
__asm {
push ecx
push esi
push edi
sub esp, argSize ;//分配 B 的可變參數的棧上空間
mov edi, esp ;//複製目標地址
lea esi, argSize
add esi, 4 ;//WriteLog 的可變數量參數地址
mov ecx, argSize
sar ecx, 2 ;//DWord Count = argSize/4;
rep movsd ;//把可變數量的參數再複製一份給 B
}
sprintf_s(buf, bufSize, _Format); //調用函數 B
__asm {
add esp, argSize ;//釋放 B 的可變參數的棧上空間
pop edi
pop esi
pop ecx
}
FILE *fp = fopen("E:\log\log.txt", "a");
if(fp != NULL) {
fputs(buf, fp);
fclose(fp);
}
return buf;
}
int main(int argc, char* argv[]) {
char buf[64];
WriteLog(buf, sizeof(buf)/sizeof(buf[0]),
"x=%c%c%c; y=%f; str=%s
",
ALIGN_4(sizeof(char)) * 3
+ ALIGN_4(sizeof(double))
+ ALIGN_4(sizeof(char*)),
0x41, 0x42, 0x43, 3.1415926, "Hello world!");
printf(buf);
return 0;
}
除此以外,就是把不同數據類型封裝成一個 union 裡面,例如 VARIANT 結構,然後在定義下面這樣的結構體:
typedef struct tagMY_ARGS {
int count; // 參數個數
VARIANT *args; // 或 VARIANT args[1];
} MY_ARGS, *PMY_ARGS;
然後寫一個函數:
void DoSomething(PMY_ARGS pMY_ARGS);
就可以了。當然,調用方要負責維護和管理傳入參數的內存。比如說,在 IE 瀏覽器控制項的 COM 操作中,頁面可以用 javascript 腳本調用 C++ 中的 COM 對象中的方法,腳本傳入的參數就是以類似這種方式傳遞的。
如果是用Qt的話,可以這樣寫:
#include &
#include &
#include &
void func(QVector& C++ 17 的 any真心無語。Read and write different types in vector 。 其他就只有C++可變參數模版和C語言的&
{
foreach(QVariant ele,vec)
{
qDebug()&<&
vec &<&< 1 &<&< "hello" &<&< 3.14;
func(vec);
return 0;
}
是這個 void foo(...){}這裡只是你說的傳入不定量的參數,至於你說的還要根據參數情況做不同的操作,那麼可以用函數重載。比方foo(int a,...); foo(int a,char c,...);之類。你把所有可能需要操作的函數參數類型都重載,到時你用的時候就可以直接使用了。
http://www.cppblog.com/woaidongmao/archive/2015/06/19/149036.html
這個大佬的博客講挺清楚:)不是使用默認參數么
你說的是 「含有可變形參的函數」吧
用initializer_list類型的形參就可以了。
initializer_list類定義在同名頭文件裡面哈。
int text(initializer_list&
我也是最近開始接觸C++,一起加油哈。
C++11 有兩種處理不同數量實參的函數
如果所有的實參類型相同,可以傳遞一個名為initializer_list的標準類型
如果實參的類型不同,可以採用一種特殊函數,所謂的可變參數模板
老的C寫法,用stdarg.h里的va_start, va_end, va_arg宏,
C++11新寫法(當然已經不算新了),用變長參數模板,template&
va_arg
這是printf的implementation用到的,典型的variable length arg functionA function may be called with a varying number of arguments of varying types. The include file &
void * params
#include &
int j = 0;
va_list arg_ptr; //第1步,定義這個指向參數列表的變數va_start(arg_ptr, i);//第2步,把上面這個變數初始化.即讓它指向參數列表while( j != -1 )
{ //第3步,獲取arg_ptr指向的當前參數.這個參數的類型由va_arg的第2個參數指定 j = va_arg(arg_ptr, int); printf("%d ",j ); } va_end(arg_ptr); //第4步,做一些清理工作}
main(){ variable(3, 3, 4, 5, 6, -1);}/////////////////////////////////////////////////////////////////////////////////////作為va_list,va_start的練習,可以學習一下使用_vsnprintf函數#include &#include &
{
formatoutput("%s%s", "d", "g");}/////////////////////////////////////////參數個數不定的函數,最頻繁使用的就是printf()與scanf()。其實,我們也可以自己實現這樣的功能,首先看一個例子:#include &", Sum(30, 20, 10, -1));//-1是參數結束標誌 return 0;}在上面的例子中,實現了一個參數個數不定的求int型和的函數Sum()。函數中一開始定義了一個va_list型變數vl,該變數用來訪問可變參數,實際上就是指針,接著使用va_start使vl指向第一個參數,然後再使用va_arg來遍歷每一個參數,va_arg返回參數列表中的當前參數並使vl指向參數列表中的下一個參數。最後通過va_end把vl指針清為NULL。在這裡,va_start,va_arg,va_end其實都是宏,那麼這些宏又是如何實現他們的功能的呢?一個很顯然的問題就是既然參數個數在函數調用之前沒有確定,所以,在函數定義的時候沒有辦法像普通函數那樣使用確定的變數來接受傳進來的參數,於是,問題的關鍵就是如何接收到這些不確定的參數了?首先,看看函數調用時實際發生的情況,在函數調用的時候,使用棧傳遞參數,拿上例來說,如果調用Sum(30, 20, 10,-1),那麼參數入棧後的情形如下圖所示(假定按照自右至左的順序入棧):既然參數在棧中的情形已經知道了,那麼,如果使用指針(程序中的vl)指向第一個參數(va_start(vl,first)),因為所有參數都是連續存放的,通過移動指針就可以訪問到每一個參數了(va_arg(vl, int)),這就是我在程序中使用到那幾個宏的實現思想。 明白了以上原理之後,就可以完全不使用宏,自己實現這樣的一個功能了,下面的程序是我按照上面的思想改寫的:#include &
", Sum(30, 20, 10, -1));//-1是參數結束標誌return 0;}可以看出,通過使用指針的確實現了參數個數不定的函數了,但是程序中還有一個問題,那就是移動指針,在程序中因為我使用的參數都是相同的int類型,所以可以事先知道訪問下一個參數的時候應該移動sizeof(int)個位元組,但是,如果參數的類型不同呢?這的確是個比較麻煩的問題,因為不同的數據類型佔用的位元組數可能是不一樣的(如double型為8個字元,short int型為2個),所以很難事先確定應該移動多少個位元組!但是辦法還是有的,這就是使用指針了,無論什麼類型的指針,都是佔用4個位元組,所以,可以把所有的傳人蔘數都設置為指針,這樣一來,就可以通過移動固定的4個位元組來實現遍歷可變參數的目的了,至於如果取得指針中的內容並使用他們,當然也是無法預先得知的了,所以這大概也就是像printf(),scanf()之類的函數還需要一個格式控制符的原因吧^_^!不過實現起來還是有不少麻煩,暫且盜用vprintf()來實現一個與printf()函數一樣功能的函數了,代碼如下:void myPrint(const char *frm, ...){ va_list vl; va_start(vl, frm); vprintf(frm, vl); va_end(vl);}//////////////////////////////////////////////書上說,當無法列出傳遞函數的所有實參的類型和數目時,可用省略號指定參數表(...)如:void foo(...);void foo(parm_list,...);void foo(...){ //...}調用:foo(a,b,c);就是不懂,把a,b,c的值傳進函數裡面後,用什麼變數來接收???如果不能接收,(...)豈不是沒意義?還有就是不明白int printf(const char*...);printf("hello,s
",userName);這個c的輸出函數是怎麼用(...)實現的.先謝了:)Re:首先函數體中聲明一個va_list,然後用va_start函數來獲取參數列表中的參數,使用完畢後調用va_end()結束。像這段代碼: void TestFun(char* pszDest, int DestLen, const char* pszFormat, ...) { va_list args; va_start(args, pszFormat); _vsnprintf(pszDest, DestLen, pszFormat, args); va_end(args); }///////////////////////////一個簡單的可變參數的C函數 先看例子程序。該函數至少有一個整數參數,其後佔位符…,表示後面參數的個數不定。在這個例子里,所有的輸入參數必須都是整數,函數的功能只是列印所有參數的值。函數代碼如下://示例代碼1:可變參數函數的使用#include "stdio.h"#include "stdarg.h"void simple_va_fun(int start, ...) { va_list arg_ptr; int nArgValue =start; int nArgCout=0; //可變參數的數目 va_start(arg_ptr,start); //以固定參數的地址為起點確定變參的內存起始地址。 do { ++nArgCout; printf("the %d th arg: %d",nArgCout,nArgValue); //輸出各參數的值 nArgValue = va_arg(arg_ptr,int); //得到下一個可變參數的值 } while(nArgValue != -1); return; }int main(int argc, char* argv[]){ simple_va_fun(100,-1); simple_va_fun(100,200,-1); return 0;}下面解釋一下這些代碼。從這個函數的實現可以看到,我們使用可變參數應該有以下步驟:
⑴由於在程序中將用到以下這些宏:
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在這裡是variable-argument(可變參數)的意思。這些宏定義在stdarg.h中,所以用到可變參數的程序應該包含這個頭文件。⑵函數里首先定義一個va_list型的變數,這裡是arg_ptr,這個變數是存儲參數地址的指針.因為得到參數的地址之後,再結合參數的類型,才能得到參數的值。
⑶然後用va_start宏初始化⑵中定義的變數arg_ptr,這個宏的第二個參數是可變參數列表的前一個參數,即最後一個固定參數。
⑷然後依次用va_arg宏使arg_ptr返回可變參數的地址,得到這個地址之後,結合參數的類型,就可以得到參數的值。
⑸設定結束條件,這裡的條件就是判斷參數值是否為-1。注意被調的函數在調用時是不知道可變參數的正確數目的,程序員必須自己在代碼中指明結束條件。至於為什麼它不會知道參數的數目,在看完這幾個宏的內部實現機制後,自然就會明白。
侵刪。
#include &推薦閱讀:
※如何評價使用後綴樹以及CritBitTree壓縮數據的PiXiu方法?
※作為一名有女(男)朋友的程序員是一種什麼體驗?
※對象沒有默認構造函數,如何定義對象數組?
※python 的 tuple 是不是冗餘設計?
※寫個編譯器,把C++代碼編譯到JVM的位元組碼可不可行?