C/C++中的可變參數和可變參數模板


1、說明

不談官方定義,就從個人理解上說,可變參數 就是函數傳參的時候,不確定傳入參數的數量和類型,從而動態地在函數內部處理,優點是,函數調用時比較靈活

2、C語言中的可變參數

C語言中一般使用宏定義實現可變參數,先看一個示例:

#include <stdarg.h>
void func(const char *fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);

    auto a = va_arg(ap, int);
    auto b = va_arg(ap, double);
    auto c = va_arg(ap, char*);
    
    cout << a << ", " << b << ", " << c << endl;
    va_end(ap);
}
int main()
{
    func("%d %f %s\n", 1, 2.0f, "hello world");
    return 0;
}

這是一個很常見的C語言的可變參數的使用,va_start 用於初始化 va_list 變量,va_arg 用於提取可變參數,va_end 用於釋放 va_list

這個示例可以用在一般函數上,無法使用在宏定義中,如果一定要在宏定義中使用,需要配合 __VA_ARGS__,示例如下:

//#define CALC(fmt, ...) func(fmt, ...) //錯誤的使用
#define CALC(fmt, ...) func(fmt, __VA_ARGS__)
int main()
{
    CALC("%d %f %s\n", 1, 2.0f, "hello world");
    return 0;
}

3、C++中的可變參數模板

C++11 中引入了新的功能,可變參數模版,語法如下:

template <typename T, typename ... Args>
void func(T t,Args ... args);

這里面,Args 稱之為模板參數包(template parameter pack),表示模板參數位置上的變長參數,

args 稱之為函數參數包(function parameter pack),表示函數參數位置上的變長參數

可以使用 sizeof...() 獲取可變參數數目

先看一個示例:

template<typename... Args>
void print(Args... args)
{
    int num = sizeof...(args);
}
int main()
{
    print(1, 2, "123", 4);
    return 0;
}

執行結果是:

4

2.1、使用遞歸的方式遍歷

可變參數一般使用遞歸的方式進行遍歷,利用模板的推導機制,每次從可變參數中取出第一個元素,直到包為空

缺點:遞歸畢竟是使用棧內存,過多的遞歸層級容易導致爆棧的發生

示例代碼如下:

void printf()
{
    cout << "end" << endl;
}
template<typename T, typename... Args>
void print(const T &value, Args... args)
{
    cout << value << endl;
    print(args...);
}
int main()
{
    print(1, 2, "333", 4);
    return 0;
}

結果輸出如下:

1
2
333
4
end

這里不是很好理解,我們可以自己理解 print() 方法的每一次調用

  1. main函數中第一次調用,value為1, args有2、"333和4三個值,輸出1;
  2. 第一次遞歸,即print中調用print,value為2,args有“333”和4兩個值,輸出2;
  3. 第二次遞歸,即print中調用print,value為“333”,args為4,輸出“333”;
  4. 第三次遞歸,即print中調用print,value為4,args無值,輸出4;
  5. 此時,args因為無值,print(args...) 語句調用的就不再是模板函數,而是第一行的 print(),輸出end;

所以,很好理解,為什么要先定義一個同名的函數,就是為了等可變參數經過幾次推導之后,沒有值的情況出現;

當然,遞歸遍歷也可以這么寫:

template<typename T>
void print(T value)
{
    cout << "end:" << value << endl;
}
template<typename T, typename... Args>
void print(const T &value, Args... args)
{
    cout << value << endl;
    print(args...);
}
int main()
{
    print(1, 2, "333", 4);
    return 0;
}

結果輸出:

1
2
333
end:4

這和第一個例子只有些許區別,函數調用如下:

  1. main函數中第一次調用,value為1, args有2、"333和4三個值,輸出1;
  2. 第一次遞歸,即print中調用print,value為2,args有“333”和4兩個值,輸出2;
  3. 第二次遞歸,即print中調用print,value為“333”,args為4,輸出“333”;
  4. 此時,args為4,print(args...) 語句調用的就不再是模板函數,而是第一行的 print(4),輸出end:4;

2.2、使用非遞歸的方式遍歷

利用 std::initializer_list ,即初始化列表展開可變參數

示例1,使用展開函數處理參數:

template<typename T>
void run(const T &t)
{
    cout << t << endl;
}
template<typename... Args>
void print(Args... args)
{
    std::initializer_list<int>{(run(args), 0)...};
}
int main()
{
    print(1, 2, "333as", 4);
    return 0;
}

示例2,使用lambda:

template<typename... Args>
void print(Args... args)
{
    std::initializer_list<int>{([&]
    { cout << args << endl; }(), 0)...};
}
int main()
{
    print(1, 2, "333as", 4);
    return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM