天天看點

cstdarg中可變參數的實作1. 可變參數的使用方法(cstdarg的使用)2. 可變參數的原理(棧中的參數清單)3. 可變參數的實作(cstdarg的實作)?4. 可變參數實作(C語言版代碼)5. 轉載請注明出處

  • 可變參數的使用方法cstdarg的使用
  • 可變參數的原理棧中的參數清單
  • 可變參數的實作cstdarg的實作
  • 可變參數實作C語言版代碼
  • 轉載請注明出處

1. 可變參數的使用方法(cstdarg的使用)

在C++中,可變參數有兩種類型:

1. 類型不變,參數個數可變

2. 參數個數可變,類型可變

今天着重講述第1種:類型不變,參數個數可變。

先看一個例子:

#include <iostream>
#include <cstdarg>

void show(int length, ...) {
    va_list ap;//定于一個指針,為char*類型
    va_start(ap, length);//讓指針指向length後面的參數,也就是我們待會要列印的參數清單的棧位址
    for (int i = ; i < length; i++)std::cout << va_arg(ap, int) << "  ";//va_arg用于取出棧中的每一個參數,同行前進一個參數的步長
    va_end(ap);//關閉該指針,置為NULL
}

int main() {
    show(, , , );
    std::cin.get();
    return ;
}
           

運作結果:

11 22 33

通過觀察運作結果,會發現:

1. 傳入的第一個參數是後面需要列印的參數的個數

2. 參數清單中使用了 … 來表示多個參數

3. 使用了多個宏

我們為了準确列印參數的個數而傳入了參數的個數,其實我們還有另外一種做法:傳入一個終止辨別

第一種做法:直接手動指定ap,不使用va_start(因為va_start使ap指針指向傳入參數的下一個位置)

#include <iostream>
#include <cstdarg>

void show();

void show(int arg1,...) {
    va_list ap = reinterpret_cast<char*>(&arg1);
    for (int i = va_arg(ap,int); i != -; i = va_arg(ap, int))std::cout << i << "  ";
    va_end(ap);
}

int main() {
    show(, , , -);
    std::cin.get();
    return ;
}
           

運作結果:

11 22 33

第二種做法:稍微修改一下循環

#include <iostream>
#include <cstdarg>

void show();

void show(int arg1, ...) {
    va_list ap;
    va_start(ap, arg1);
    //for (int i = va_arg(ap, int); i != -1; i = va_arg(ap, int))std::cout << i << "  ";
    for (int i = arg1; i != -; i = va_arg(ap, int))std::cout << i << "  ";
    va_end(ap);
}

int main() {
    show(, , , -);
    std::cin.get();
    return ;
}
           

由此,我們可以總結出cstdarg頭檔案當中可變參數宏的用法的一般步驟:

1. va_list ap; 建立一個指向棧中存儲參數清單位置的char類型指針ap

2. va_start(ap,v); 初始化ap,讓ap指向與v相鄰的下一個參數

3. va_arg(ap,type); 傳入ap,并且傳入參數的類型type,一個一個地取出存儲在棧中的參數。

4. va_end(ap); 操作結束,将ap置為NULL

2. 可變參數的原理(棧中的參數清單)

注意:有些棧可能是從低位址向高位址增長,有些可能不是,這要根據具體情來判斷,下面僅僅是一種比較常見的棧

cstdarg中可變參數的實作1. 可變參數的使用方法(cstdarg的使用)2. 可變參數的原理(棧中的參數清單)3. 可變參數的實作(cstdarg的實作)?4. 可變參數實作(C語言版代碼)5. 轉載請注明出處

3. 可變參數的實作(cstdarg的實作)?

這是實作代碼當中的一種:

#include <iostream>

//擷取傳入參數的位址
#define _ADDRESSOF(v) (&reinterpret_cast<const char&>(v))
//擷取與int對齊的元素大小,比如 1位元組=4位元組 2位元組=4位元組 5位元組=8位元組
//因為某些系統的棧實作時遵循記憶體對齊的規則
#define _INTSIZEOF(v) ((sizeof(v)+sizeof(int)-1)&~(sizeof(int)-1))
//定義一個字元型指針,指向某個參數的起始位址
#define va_list char*
//當ap用完時,置空ap,防止出現野指針
#define va_end(ap) (ap=reinterpret_cast<va_list>(0))
//初始化指向棧中參數清單的指針ap,讓他指向v的下一個參數的位置
#define va_start(ap,v) (ap=const_cast<va_list>(_ADDRESSOF(v))+_INTSIZEOF(v))
//通過一個指向棧的指針ap,一個一個的取出參數清單中的參數
#define va_arg(ap,t) (*reinterpret_cast<t*>((ap+=_INTSIZEOF(t))-_INTSIZEOF(t)))

//使用它
void show(int a,...){
    va_list argp;
    va_start(argp,a);
    for(int i=;i<a;i++)std::cout<<va_arg(argp,int)<<std::endl;
    va_end(argp);
}

int main(){
    show(,,,);
    return ;
}
           

接下來我們将會一步一步地解析它實作的過程。

參考上文中提到的可變參數用法的一般過程:

由此,我們可以總結出cstdarg頭檔案當中可變參數宏的用法的一般步驟:

1. va_list ap; 建立一個指向棧中存儲參數清單位置的char類型指針ap

2. va_start(ap,v); 初始化ap,讓ap指向與v相鄰的下一個參數

3. va_arg(ap,type); 傳入ap,并且傳入參數的類型type,一個一個地取出存儲在棧中的參數。

4. va_end(ap); 操作結束,将ap置為NULL

我們可以總結出實作可變參數需要的操作分别有哪些:

  1. va_list 我們要建立一個ap,指向棧 -> 需要:建立ap的操作
  2. va_start 我們要初始化ap -> 需要:取位址操作并初始化ap
  3. 有些棧的實作遵循記憶體對齊原則 -> 需要:記憶體對齊操作
  4. va_arg 我們 -> 需要:一個一個地取出參數清單中的參數
  5. va_end 置空ap -> 需要:置空我們建立的指針ap

—- 步驟詳解:—-

  • 建立一個ap
    //定義一個字元型指針,指向某個參數的起始位址
    
    #define va_list char*
               

  • 取位址操作
    //擷取傳入參數的位址,reinterpret_cast負責實作類型強轉
    
    #define _ADDRESSOF(v) (&reinterpret_cast<const char&>(v))
               
為什麼使用reinterpret_cast,因為該轉換可以實作兩種完全不相關的類型的之間的轉換(重新闡釋比特位),比如:傳入int類型,但是需要讓ap指向int資料的起始位址,此時需要将int類型轉換為int的const引用,這樣我們取得的位址将會是const類型的,可以保證棧中資料不被意外修改

  • 初始化ap操作
    //初始化指向棧中參數清單的指針ap,讓他指向v的下一個參數的位置
    
    #define va_start(ap,v) (ap=const_cast<va_list>(_ADDRESSOF(v))+_INTSIZEOF(v))
               
const_cast去掉取出位址中的const屬性,在指派給ap(char*類型)之前,“+_INTSIZEOF(v)”操作就是将指針移動到v後面的一個元素的起始位址。

  • 記憶體對齊操作
    //擷取與int對齊的元素大小,比如 1位元組=4位元組 2位元組=4位元組 5位元組=8位元組
    //因為某些系統的棧實作時遵循記憶體對齊的規則
    
    #define _INTSIZEOF(v) ((sizeof(v)+sizeof(int)-1)&~(sizeof(int)-1))
               
參考博文:CC++可變參數stdarg.h中的餘數運用

  • 取出參數
    //通過一個指向棧的指針ap,一個一個的取出參數清單中的參數
    
    #define va_arg(ap,t) (*reinterpret_cast<t*>((ap+=_INTSIZEOF(t))-_INTSIZEOF(t)))
               

ap指向的就是棧中的某個元素的起始位址,此時我們隻需将指針轉換為t類型,最後用*取出該資料即可。

關于“(ap+=_INTSIZEOF(t)”是為了讓ap移動到下一個參數的起始位址,傳回之前“-_INTSIZEOF(t)”就是由于前面+=操作将指針移動到了下一個參數的記憶體位址,是以我們要将之減去後再取出。

  • 置空ap指針
    //當ap用完時,置空ap,防止出現野指針
    
    #define va_end(ap) (ap=reinterpret_cast<va_list>(0))
               
将NULL的指派給ap

以上就是cstdarg的實作。

4. 可變參數實作(C語言版代碼)

#include <stdio.h>

//擷取傳入參數的位址
#define _ADDRESSOF(v) ((const char*)&v)
//擷取與int對齊的元素大小,比如 1位元組=4位元組 2位元組=4位元組 5位元組=8位元組
//因為某些系統的棧實作時遵循記憶體對齊的規則
#define _INTSIZEOF(v) ((sizeof(v)+sizeof(int)-1)&~(sizeof(int)-1))
//定義一個字元型指針,指向某個參數的起始位址
#define va_list char*
//當ap用完時,置空ap,防止出現野指針
#define va_end(ap) (ap=(va_list)0)
//初始化指向棧中參數清單的指針ap,讓他指向v的下一個參數的位置
#define va_start(ap,v) (ap=(va_list)(_ADDRESSOF(v))+_INTSIZEOF(v))
//通過一個指向棧的指針ap,一個一個的取出參數清單中的參數
#define va_arg(ap,t) (*(t*)((ap+=_INTSIZEOF(t))-_INTSIZEOF(t)))

//使用它
void show(int a,...){
    va_list argp;
    va_start(argp,a);
    for(int i=;i<a;i++)printf("%d\n",va_arg(argp,int));
    va_end(argp);
}

int main(){
    show(,,,);
    return ;
}
           

5. 轉載請注明出處

繼續閱讀