日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

可變參C API va_list,va_start,va_arg_va_end以及c++可變參模板

作者:四庫全書的酷 更新時間: 2022-05-13 編程語言

文章目錄

  • C變參API
    • C變參API函數原型
    • C變參API實現源碼
    • C變參API應用實例
        • C 變參函數缺點
  • C++變參實現
    • 方法
      • initializer_list 形參
      • 可變參數模板

C變參API

C變參API函數原型

va_list ap   /* 就等價于char *ap,定義一個指針 */

va_start(ap, format)    /* 就是將指針ap移動到第一個可變參數的地方,即ap = ap + sizeif(char *) */

va_arg(ap, 變量類型)     /* 返回當前ap指針指向的值,然后指向下一個可變參數 */

va_end                   /* 將指針請為NULL */

C變參API實現源碼

typedef char *  va_list;

#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

#define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,t)    ap += _INTSIZEOF(t), **t)(ap - _INTSIZEOF(t)
#define va_end(ap)      ( ap = (va_list)0 )

C變參API應用實例

C 語言中,有時需要變參函數來完成特殊的功能,比如 C 標準庫函數 printf() 和 scanf()。C 中提供了省略符…能夠幫住 coder 完成變參函數的書寫。變參函數原型申明如下:

type functionname(type param1,...);

變參函數至少要有一個固定參數,省略號“…”不可省略,比如 printf() 原型如下:

int printf(const char *format,...);

在頭文件 stdarg.h 中定義了三個宏函數用于獲取指定類型的實參:

void	va_start(va_list arg,prev_param);    
type    va_arg(va_list arg,type);
void    va_end(va_list arg);

va 在這里是 可變參數(variable argument)的意思,借助上面三個宏函數,變參函數的實現就變得相對簡單很多。一般的變參函數處理過程:

(1)定義一個 va_list 變量設為 va;
(2)調用 va_start() 使得va存放變參函數的變參前的一個固定參數的地址;
(3)不斷調用 va_arg() 使得va指向下一個實參;
(4)最后調用 va_end() 表示變參處理完成,將 va 置空。

原理就是:函數的參數在內存中從低地址向高地址依次存放。

看一個例子:模仿 pritnf() 的實現:

#include  
#include  
#include  
using namespace std;  
  
void func(char *c,...) {        
    int i=0;  
    double result=0;  
	va_list arg;        //va_list變量  
    va_start(arg,c);    //arg指向固定參數c  
    while(c[i]!='\0') {      
        if(c[i]=='%'&&c[i+1]=='d')
        {  
            printf("%d",va_arg(arg,int));  
            i++;  
        } else if(c[i]=='%'&&c[i+1]=='f') {  
            printf("%f",va_arg(arg,double));  
            i++;  
        } else {
            putchar(c[i]);
        } 
        i++;  
    }  
    va_end(arg);  
}  
  
int main() {  
    int i=100;  
    double j=100.0;  
    printf("%d be equal %f\n",i,j);  
    func("%d be equal %f\n",i,j);
	system("pause");
}

C 變參函數缺點

  • 缺乏類型檢查,容易出現不合理的強制類型轉換。在獲取實參時,是通過給定的類型進行獲取,如果給定的類型與實際參數類型不符,則會出現類型安全性問題,容易導致獲取實參失敗。
  • 不支持自定義類型。自定義類型在程序中經常用到,比如我們要使用printf()來打印一個Student類型的對象的內容,該用什么格式字符串去指定實參類型,通過C提供的va_list,我們無法提取實參內容。
    鑒于以上兩點,李健老師在其著作《編寫高質量代碼改善C++程序的150個建議》建議盡量不要使用 C 風格的變參函數

C++變參實現

方法

為了編寫能夠處理不同數量實參的函數,C++11提供了兩種主要方法:

  • 如果所有實參類型相同,可以傳遞標準庫類型initializer_list;
  • 如果實參類型不同,可以編寫一種特殊的函數,也就是所謂的可變參數模板。

initializer_list 形參

initializer_list 是 C++11 引入的一種標準庫類模板,用于表示某種特定類型值的數組。initializer_list類型定義在同名的頭文件中,它提供的操作有:

initializer_list<T> lst;			//默認初始化T類型的空列表。
initializer_list<T> lst{a,b,c,...};	//lst的元素是對應初始值的副本,且列表中的元素是const。
lst2(lst);		//拷貝構造一個initializer_list對象,不拷貝列表中的元素,與原始列表共享元素
lst2=lst;		//賦值,與原始列表共享元素。

lst.size();		//列表中的元素數量。
lst.begin();	//返回指向lst中首元素的指針。
lst.end();		//返回lst中尾元素下一位置的指針。

和vector與list一樣,initializer_list也是一種模板類型,定義initializer_list對象時必須指明列表中所含元素的類型。與vector和list不同之處在于initializer_list中的元素不可修改,并且拷貝構造和賦值時元素不會被拷貝。如此設計,讓initializer_list更加符合參數通過指針傳遞,而非值傳遞,提高性能。所以C++11采用了initializer_list作為變參函數的形參,下面給出一個打印錯誤的變參函數:

void error_msg(initializer\_list<string> il) {
	for(auto beg=il.begin();beg!=il.end()) {
		cout<<*beg<<" ";
	}
	cout<<endl;
}

可變參數模板

簡介:
目前大部分主流編譯器的最新版本均支持了C++11標準(官方名為ISO/IEC14882:2011)大部分的語法特性,其中比較難理解的新語法特性可能要屬可變參數模板(variadic template)了,GCC 4.6和Visual studio 2013都已經支持變參模板??勺儏的0寰褪且粋€接受可變數目參數的函數模板或類模板。可變數目的參數被稱為參數包(parameter packet),這個也是新引入C++的概念,可以細分為兩種參數包:
(1)模板參數包(template parameter packet),表示零個或多個模板參數。
(2)函數參數包(function parameter packet),表示零個或多個函數參數。

可變參數模板示例:
使用省略號來指明一個模板的參數包,在模板參數列表中,class…或typename…指出接下來的參數表示零個或多個類型參數;一個類型名后面跟一個省略號表示零個或多個給定類型的非類型參數。聲明一個帶有可變參數個數的模板的語法如下所示:

// 1.申明可變參數的類模板
template<typename... Types> class tuple;
tuple<int, string> a;  // use it like this

// 2.申明可變參數的函數模板
template<typename T,typename... Types> void foo(const T& t,const Types&... rest);
foo<int,float,double,string>(1,2.0,3.0,"lvlv");//use like this

// 3.申明可變非類型參數的函數模板(可變非類型參數也可用于類模板)
template<typename T,unsigned... args> void foo(const T& t);
foo<string,1,2>("lvlv");//use like this

其中第一條示例中Types就是模板參數包,第二條示例中rest就是函數參數包,第三條示例中args就是非類型模板參數包。

參數包擴展:
現在我們知道parameter packet了,怎么在程序中真正具體地去處理打包進來的“任意個數”的參數呢?也就是說可變參數模板,我們如何進行參數包的擴展,獲取傳入的參數包中的每一個實參呢?對于一個參數包,可以通過運算符sizeof…來獲取參數包中的參數個數,比如:

template<typename... Types> void g(Types... args) {
	cout<<sizeof...(Types)<<endl;  //類型參數數目
	cout<<sizeof...(args)<<endl;   //函數參數數目
}

我們能夠對參數包唯一能做的事情就是對其進行擴展,擴展一個包就是將它分解為構成的元素,通過在參數包的右邊放置一個省略號來觸發擴展操作,例如:

template<typename T,typename... Types> ostream& print(ostream& os,const T& t,const Types&... rest) {
	os<<t<<",";
	return print(os,rest...);
}

上面的示例代碼中,存在兩種包擴展操作:
(1)const Types&… rest表示模板參數包的擴展,為print函數生成形參列表;
(2)對print的調用中rest…表示函數參數包的擴展,為print調用生成實參列表。

可變參數函數實例:
可變參數函數通常以遞歸的方式來獲取參數包的每一個參數。第一步調用處理包中的第一個實參,然后用剩余實參調用自身。最后,定義一個非可變參數的同名函數模板來終止遞歸。我們以自定義的print函數為例,實現如下:

#include   
using namespace std;

template<typename T> ostream& print(ostream& os,const T& t){
	os<<t<<endl;  //包中最后一個元素之后打印換行符
}

template<typename T,typename... Types> ostream& print(ostream& os,const T& t,const Types&... rest){
	os<<t<<",";  //打印第一個實參
	print(os,rest...);  //遞歸調用,打印其他實參
}

int main(){
	print(cout,10,123.0,"lvlv",1); //例1
	print(cout,1,"lvlv0","lvlv1");
}

程序輸出:

10,123,lvlv,1
1,lvlv0,lvlv1

上面遞歸調用print,以例1為例,執行的過程如下:

在這里插入圖片描述

前三個調用只能與可變參數版本的print匹配,非變參版本是不可行的,因為這三個調用要傳遞兩個以上實參,非可變參數的print只接受兩個實參。對于最后一次遞歸調用print(cout,1),兩個版本的print都可以,因為這個調用傳遞兩個實參,第一個實參的類型為ostream&,另一個是const T&參數。但是由于非可變參數模板比可變參數模板更加特例化,因此編譯器選擇非可變參數版本。

原文鏈接:https://blog.csdn.net/weixin_46535567/article/details/124698937

欄目分類
最近更新