網(wǎng)站首頁(yè) 編程語(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
相關(guān)推薦
- 2021-12-02 C語(yǔ)言GetStdHandle函數(shù)使用方法_C 語(yǔ)言
- 2022-08-28 Golang正則表達(dá)式判斷手機(jī)號(hào)或身份證方法實(shí)例_Golang
- 2022-05-22 redis數(shù)據(jù)結(jié)構(gòu)之壓縮列表_Redis
- 2022-09-07 Go編寫定時(shí)器與定時(shí)任務(wù)詳解(附第三方庫(kù)gocron用法)_Golang
- 2022-08-21 Python?海象運(yùn)算符(?:=)的三種用法_python
- 2023-01-30 PyQt中使用QProcess運(yùn)行一個(gè)進(jìn)程的示例代碼_python
- 2022-12-28 Python?PyQt5中窗口數(shù)據(jù)傳遞的示例詳解_python
- 2022-03-24 Android實(shí)現(xiàn)旋轉(zhuǎn)動(dòng)畫_Android
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支