標籤:

c++函數如何接受數量不定的函數參數?


template&
void func(TArgs... args) {
auto arguments = std::forward_as_tuple(std::forward&(args)...);
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& vec)
{
foreach(QVariant ele,vec)
{
qDebug()&<& vec;
vec &<&< 1 &<&< "hello" &<&< 3.14; func(vec); return 0; }

C++ 17 的 any真心無語。Read and write different types in vector 。

其他就只有C++可變參數模版和C語言的&了。


是這個 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& il){//函數內容}

我也是最近開始接觸C++,一起加油哈。


C++11 有兩種處理不同數量實參的函數

如果所有的實參類型相同,可以傳遞一個名為initializer_list的標準類型

如果實參的類型不同,可以採用一種特殊函數,所謂的可變參數模板


老的C寫法,用stdarg.h里的va_start, va_end, va_arg宏,

C++11新寫法(當然已經不算新了),用變長參數模板,template&


va_arg

這是printf的implementation用到的,典型的variable length arg function

A function may be called with a varying number of arguments of varying types. The include file & declares a type va_list and defines three macros for stepping through a list of arguments whose number and types are not known to the called function

documentation:

https://linux.die.net/man/3/va_arg


void * params


#include &

#include & //要包含這個頭文件

void variable(int i, ...)

{

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 &

void formatoutput(char* format, ...)

{

char s[10];

va_list arg_ptr;

va_start(arg_ptr, format);

_vsnprintf(s, sizeof(s)-1, format, arg_ptr);

printf("%s ", s);

}

void main()

{

formatoutput("%s%s", "d", "g");

}

////////////////////

/////////////////////

參數個數不定的函數,最頻繁使用的就是printf()與scanf()。其實,我們也可以自己實現這樣的功能,首先看

一個例子:

#include &

#include &

int Sum(int first, int second, ...)

{

int sum = 0, t = first;

va_list vl;

va_start(vl, first);

while (t != -1)

{

sum += t;

t = va_arg(vl, int);//將當前參數轉換為int類型

}

va_end(vl);

return sum;

}

int main(int argc, char* argv[])

{

printf("The sum is %d
", 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 &

int Sum(int first, int second, ...)

{

int sum = 0, t = first;

char * vl;//定義一個指針

vl = (char *)first;//使指針指向第一個參數

while (*vl != -1)//-1是預先給定的結束符

{

sum += *(int *)vl;//類型轉換

vl += sizeof(int);//移動指針,使指針指向下一個參數

}

return sum;

}

int main(int argc, char* argv[])

{

printf("The sum is %d
", 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 &

#include "stdarg.h"

using namespace std;

int sum(int count,...)

{

if(count&<=0)

return 0;

va_list arg_ptr;

va_start(arg_ptr,count);

int sum = 0;

for (int i=0;i& {

sum+=va_arg(arg_ptr,int);

}

va_end(arg_ptr);

return sum;

}

int main()

{

cout&<& cout&<& return 0;

}


推薦閱讀:

如何評價使用後綴樹以及CritBitTree壓縮數據的PiXiu方法?
作為一名有女(男)朋友的程序員是一種什麼體驗?
對象沒有默認構造函數,如何定義對象數組?
python 的 tuple 是不是冗餘設計?
寫個編譯器,把C++代碼編譯到JVM的位元組碼可不可行?

TAG:C | CC | C入門 |