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

學(xué)無(wú)先后,達(dá)者為師

網(wǎng)站首頁(yè) 編程語(yǔ)言 正文

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

作者:四庫(kù)全書的酷 更新時(shí)間: 2022-05-13 編程語(yǔ)言

文章目錄

  • C變參API
    • C變參API函數(shù)原型
    • C變參API實(shí)現(xiàn)源碼
    • C變參API應(yīng)用實(shí)例
        • C 變參函數(shù)缺點(diǎn)
  • C++變參實(shí)現(xiàn)
    • 方法
      • initializer_list 形參
      • 可變參數(shù)模板

C變參API

C變參API函數(shù)原型

va_list ap   /* 就等價(jià)于char *ap,定義一個(gè)指針 */

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

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

va_end                   /* 將指針請(qǐng)為NULL */

C變參API實(shí)現(xiàn)源碼

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應(yīng)用實(shí)例

C 語(yǔ)言中,有時(shí)需要變參函數(shù)來(lái)完成特殊的功能,比如 C 標(biāo)準(zhǔn)庫(kù)函數(shù) printf() 和 scanf()。C 中提供了省略符…能夠幫住 coder 完成變參函數(shù)的書寫。變參函數(shù)原型申明如下:

type functionname(type param1,...);

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

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

在頭文件 stdarg.h 中定義了三個(gè)宏函數(shù)用于獲取指定類型的實(shí)參:

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

va 在這里是 可變參數(shù)(variable argument)的意思,借助上面三個(gè)宏函數(shù),變參函數(shù)的實(shí)現(xiàn)就變得相對(duì)簡(jiǎn)單很多。一般的變參函數(shù)處理過(guò)程:

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

原理就是:函數(shù)的參數(shù)在內(nèi)存中從低地址向高地址依次存放。

看一個(gè)例子:模仿 pritnf() 的實(shí)現(xiàn):

#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指向固定參數(shù)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 變參函數(shù)缺點(diǎn)

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

C++變參實(shí)現(xiàn)

方法

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

  • 如果所有實(shí)參類型相同,可以傳遞標(biāo)準(zhǔn)庫(kù)類型initializer_list;
  • 如果實(shí)參類型不同,可以編寫一種特殊的函數(shù),也就是所謂的可變參數(shù)模板。

initializer_list 形參

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

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

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

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

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

可變參數(shù)模板

簡(jiǎn)介:
目前大部分主流編譯器的最新版本均支持了C++11標(biāo)準(zhǔn)(官方名為ISO/IEC14882:2011)大部分的語(yǔ)法特性,其中比較難理解的新語(yǔ)法特性可能要屬可變參數(shù)模板(variadic template)了,GCC 4.6和Visual studio 2013都已經(jīng)支持變參模板。可變參數(shù)模板就是一個(gè)接受可變數(shù)目參數(shù)的函數(shù)模板或類模板??勺償?shù)目的參數(shù)被稱為參數(shù)包(parameter packet),這個(gè)也是新引入C++的概念,可以細(xì)分為兩種參數(shù)包:
(1)模板參數(shù)包(template parameter packet),表示零個(gè)或多個(gè)模板參數(shù)。
(2)函數(shù)參數(shù)包(function parameter packet),表示零個(gè)或多個(gè)函數(shù)參數(shù)。

可變參數(shù)模板示例:
使用省略號(hào)來(lái)指明一個(gè)模板的參數(shù)包,在模板參數(shù)列表中,class…或typename…指出接下來(lái)的參數(shù)表示零個(gè)或多個(gè)類型參數(shù);一個(gè)類型名后面跟一個(gè)省略號(hào)表示零個(gè)或多個(gè)給定類型的非類型參數(shù)。聲明一個(gè)帶有可變參數(shù)個(gè)數(shù)的模板的語(yǔ)法如下所示:

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

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

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

參數(shù)包擴(kuò)展:
現(xiàn)在我們知道parameter packet了,怎么在程序中真正具體地去處理打包進(jìn)來(lái)的“任意個(gè)數(shù)”的參數(shù)呢?也就是說(shuō)可變參數(shù)模板,我們?nèi)绾芜M(jìn)行參數(shù)包的擴(kuò)展,獲取傳入的參數(shù)包中的每一個(gè)實(shí)參呢?對(duì)于一個(gè)參數(shù)包,可以通過(guò)運(yùn)算符sizeof…來(lái)獲取參數(shù)包中的參數(shù)個(gè)數(shù),比如:

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

我們能夠?qū)?shù)包唯一能做的事情就是對(duì)其進(jìn)行擴(kuò)展,擴(kuò)展一個(gè)包就是將它分解為構(gòu)成的元素,通過(guò)在參數(shù)包的右邊放置一個(gè)省略號(hào)來(lái)觸發(fā)擴(kuò)展操作,例如:

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

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

可變參數(shù)函數(shù)實(shí)例:
可變參數(shù)函數(shù)通常以遞歸的方式來(lái)獲取參數(shù)包的每一個(gè)參數(shù)。第一步調(diào)用處理包中的第一個(gè)實(shí)參,然后用剩余實(shí)參調(diào)用自身。最后,定義一個(gè)非可變參數(shù)的同名函數(shù)模板來(lái)終止遞歸。我們以自定義的print函數(shù)為例,實(shí)現(xiàn)如下:

#include   
using namespace std;

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

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

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

程序輸出:

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

上面遞歸調(diào)用print,以例1為例,執(zhí)行的過(guò)程如下:

在這里插入圖片描述

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

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

欄目分類
最近更新