- 可變參數的使用方法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. 可變參數的原理(棧中的參數清單)
注意:有些棧可能是從低位址向高位址增長,有些可能不是,這要根據具體情來判斷,下面僅僅是一種比較常見的棧
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
我們可以總結出實作可變參數需要的操作分别有哪些:
- va_list 我們要建立一個ap,指向棧 -> 需要:建立ap的操作
- va_start 我們要初始化ap -> 需要:取位址操作并初始化ap
- 有些棧的實作遵循記憶體對齊原則 -> 需要:記憶體對齊操作
- va_arg 我們 -> 需要:一個一個地取出參數清單中的參數
- va_end 置空ap -> 需要:置空我們建立的指針ap
—- 步驟詳解:—-
- 建立一個ap
//定義一個字元型指針,指向某個參數的起始位址 #define va_list char*
–
為什麼使用reinterpret_cast,因為該轉換可以實作兩種完全不相關的類型的之間的轉換(重新闡釋比特位),比如:傳入int類型,但是需要讓ap指向int資料的起始位址,此時需要将int類型轉換為int的const引用,這樣我們取得的位址将會是const類型的,可以保證棧中資料不被意外修改
- 取位址操作
//擷取傳入參數的位址,reinterpret_cast負責實作類型強轉 #define _ADDRESSOF(v) (&reinterpret_cast<const char&>(v))
–
const_cast去掉取出位址中的const屬性,在指派給ap(char*類型)之前,“+_INTSIZEOF(v)”操作就是将指針移動到v後面的一個元素的起始位址。
- 初始化ap操作
//初始化指向棧中參數清單的指針ap,讓他指向v的下一個參數的位置 #define va_start(ap,v) (ap=const_cast<va_list>(_ADDRESSOF(v))+_INTSIZEOF(v))
–
參考博文:CC++可變參數stdarg.h中的餘數運用
- 記憶體對齊操作
//擷取與int對齊的元素大小,比如 1位元組=4位元組 2位元組=4位元組 5位元組=8位元組 //因為某些系統的棧實作時遵循記憶體對齊的規則 #define _INTSIZEOF(v) ((sizeof(v)+sizeof(int)-1)&~(sizeof(int)-1))
–
- 取出參數
//通過一個指向棧的指針ap,一個一個的取出參數清單中的參數 #define va_arg(ap,t) (*reinterpret_cast<t*>((ap+=_INTSIZEOF(t))-_INTSIZEOF(t)))
ap指向的就是棧中的某個元素的起始位址,此時我們隻需将指針轉換為t類型,最後用*取出該資料即可。
關于“(ap+=_INTSIZEOF(t)”是為了讓ap移動到下一個參數的起始位址,傳回之前“-_INTSIZEOF(t)”就是由于前面+=操作将指針移動到了下一個參數的記憶體位址,是以我們要将之減去後再取出。
–
将NULL的指派給ap
- 置空ap指針
//當ap用完時,置空ap,防止出現野指針 #define va_end(ap) (ap=reinterpret_cast<va_list>(0))
–
以上就是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 ;
}