免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
【洛谷日報#226】C 可變參數(shù)入門

由于本人是個蒟蒻,并且語文不好,所以如果本文有任何錯誤,請指出,我將盡快修正。

1.可能需要的知識

  • 子函數(shù)的編寫(必需)

  • C++的類型

  • 強制類型轉(zhuǎn)換

  • 指針/迭代器

  • auto

  • 模板

2.引言

假如wenge問你:

如何不用for,求出2個數(shù)的最大值?

你說,這還不簡單,

ans=max(a,b);

wenge又問你:

如何不用for,求出3個數(shù)的最大值?

你說,這還不簡單,

ans=max(a,max(b,c));

wenge又問你:

如何不用for,求出10個數(shù)的最大值?

你說,這還不簡單,

ans=max(a[1],max(a[2],max(a[3],max(a[4],max(a[5],max(a[6],max(a[7],max(a[8],max(a[9],a[10])))))))));

wenge說這太亂了。

你說,這個總行了吧,

ans=max(ans,a[1]);
ans=max(ans,a[2]);
ans=max(ans,a[3]);
ans=max(ans,a[4]);
ans=max(ans,a[5]);
ans=max(ans,a[6]);
ans=max(ans,a[7]);
ans=max(ans,a[8]);
ans=max(ans,a[9]);
ans=max(ans,a[10]);

能不能一行寫完呢?

能。

ans=max({a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8],a[9],a[10]});//C++11

但是庫函數(shù)max為什么可以為什么可以這樣使用呢?

好了,現(xiàn)在讓我們進入正題——C++的兩種可變參數(shù)。

3.C++ STL的可變參數(shù)(initializer_list)

注意:此節(jié)為C++11的新特性,編譯時請增加命令行選項-std=c++11(這個貌似大家都懂)

1)什么是initializer_list

首先讓我們看看max的函數(shù)模板聲明。

注意到用紅色框起來的函數(shù)模板聲明。

這個函數(shù)以initializer_list為參數(shù)。initializer_list是啥?

就是一個初始化表。初始化表是啥?讓我們看一個例子。

{1,2,3,4,5}

什么?你告訴我這個就是initializer_list?讓我們再看一個例子。

vector<int> a={1,2,3,4,5};

如果你還不知道initializer_list是啥,那就再看一個例子。

int a[5]={1,2,3,4,5};

你可能會問了,這不是數(shù)組賦值嗎?

但是,這個數(shù)組賦值用了初始化表,所以{1,2,3,4,5}就是一個initializer_list。

initializer_list是C++11的一個新特性。這玩意除了能給數(shù)組賦值以外,還可以給自己定義的結(jié)構(gòu)體賦值。比如

#include <iostream>
#include <initializer_list>
struct point{
    int x,y;
};

int main(){
    point a={1,2};
    cout<<a.x<<' '<<a.y;
    return 0;
}

程序會輸出1 2。然而我們要講的是initializer_list實現(xiàn)可變參數(shù)。

2)initializer_list實現(xiàn)可變參數(shù)

initializer_list實現(xiàn)可變參數(shù)的方式,就是把initializer_list作為函數(shù)的參數(shù)。

現(xiàn)在首先看一看本人實現(xiàn)的max函數(shù):

#include <iostream>
#include <algorithm>
#include <initializer_list>
using namespace std;
//C++ STL的可變參數(shù) 

int mymax(initializer_list<int> a){
    int ans=-2147483648;//int的最小值
    for(auto i:a){
        ans=max(i,ans);
    }
    return ans;
}

int main(){
    int a=1,b=2,c=3,d=4,e=5;
    cout<<mymax({a,b,c,d,e});
    return 0;
}

程序輸出了5,因為a,b,c,d,e的最大值就是5。

現(xiàn)在讓我們看看mymax這個函數(shù)是如何實現(xiàn)的。

首先定義了一個臨時變量ans存儲答案。

現(xiàn)在來看for。也許可能有人不明白這個for是什么意思。實際上這個for的用途是遍歷整個initializer_list。

比較麻煩的一點是,簡單的遍歷一個initializer_list只有兩種方式,一種是使用C++11的auto新特性(也就是代碼里的for(auto i:a)),另外一個是使用迭代器。使用迭代器的方法是這樣的:

int mymax(initializer_list<int> a){
    int ans=-2147483648;//int的最小值
    for(initializer_list<int>::iterator i=a.begin();i!=a.end();i++){
        ans=max(*i,ans);
    }
    return ans;
}

看上去很麻煩。但是initializer_list<int>::iterator這東西可以換成auto。但是即使initializer_list<int>::iterator可以換成auto,個人還是推薦使用for(auto i:a)

但是上述方法只能遍歷整個initializer_list容器。如何遍歷一個initializer_list容器的一部分?一個簡單的方法是建立一個數(shù)組,把initializer_list中所有的數(shù)據(jù)拷貝進這個數(shù)組里。

一個initializer_list的大小可以用initializer_list.size()函數(shù)來獲取。initializer_list類型的size()函數(shù)與stringvectorsize()的作用完全一致,即返回initializer_list中的元素個數(shù)??纯聪旅娴睦樱?/p>

#include <iostream>
#include <algorithm>
#include <initializer_list>
using namespace std;

int b[10];

int average(initializer_list<int> a){
    int ans=0;
    int j=1;
    for(auto i:a){
        b[j]=i;
        j++;
    }
    sort(b+1,b+a.size()+1);
    for(int i=2;i<a.size();i++){
        ans+=b[i];
    }
    ans/=(a.size()-2);
    return ans;
}
struct point{
    int x,y;
};

int main(){
    int a=80,b=10,c=40,d=40,e=70;
    cout<<average({a,b,c,d,e});
    return 0;
}

這個例子是求多個數(shù)的去除一個最大值和一個最小值的平均值??梢钥吹?,我們將initializer_list類型的a拷貝進了預(yù)先定義的數(shù)組b,再在數(shù)組b上執(zhí)行sort()

你可以在單個函數(shù)使用多個initializer_list作為參數(shù)。比如

void print(initializer_list<int> a,initializer_list<char> b);//print()的具體定義略
int main(){
    print({1,2,3},{'a','b','c'});
    return 0;
}

這樣使用是合法的。

并且你可以增加一個模板,使一個initializer_list可以支持同種不同類型的數(shù)據(jù):

template<typename T>
void print(initializer_list<T> a);//print()的具體定義略
int main(){
    print({114514,1919,810});
    print({'c','h','a','r'});
    print({'xyzzy','plugh','abracadabra'});
    return 0;
}

3)類型轉(zhuǎn)換問題以及其他需要注意的地方

首先,因為initializer_list是拿大括號括起來的,所以傳入一個initializer_list參數(shù)的時候要拿大括號括起來。

C++標(biāo)準(zhǔn)要求“initializer_list的元素類型都必須相同,但編譯器將進行必要的轉(zhuǎn)換”、“但不能進行隱式的窄化轉(zhuǎn)換”(C++ Primer)。比如下面的代碼:

double a[5]={1.14514,2.33,-3.456789,4.0,5};
//int類型的5被編譯器隱式轉(zhuǎn)換成double類型的5.0 

long long b[5]={114514ll,233ll,-3456789ll,4ll,5};
//int類型的5被編譯器隱式轉(zhuǎn)換成long long類型的5 

int c[5]={114514,233,-3456789,4,5.0};
//double類型的5.0被編譯器轉(zhuǎn)換成int,屬于隱式窄化轉(zhuǎn)換,錯誤 

但是在C++ Primer里所說的這個錯誤并不一定導(dǎo)致CE,比如本人在gcc4.9.2編譯,只出現(xiàn)了一個warning。但是為了避免潛在的CE,應(yīng)該盡量避免類型轉(zhuǎn)換問題。

除了類型轉(zhuǎn)換問題,initializer_list的元素類型都必須相同,所以不能將不同類型的元素裝進initializer_list里。例如下列代碼將會導(dǎo)致CE:

void print(initializer_list<int> a);//print()的具體定義略
int main(){
    print('string');
    return 0;
}

另外,即使你使用了多個initializer_list,由于每個initializer_list的元素類型都必須相同,每個initializer_list只能處理一種類型的數(shù)據(jù)。這導(dǎo)致了一般的initializer_list實現(xiàn)的可變參數(shù)只能支持單種類型的數(shù)據(jù)。

4.C的可變參數(shù)(va_list)

1)從scanf和printf說起

另外,說起可變參數(shù),我們可能想到最多的例子就是scanfprintf。實際上,它們的確是真真正正的可變參數(shù)函數(shù)。而scanfprintf是C的原生函數(shù),其誕生早在initializer_list之前。那么scanfprintf是如何實現(xiàn)的呢?

還是看一下函數(shù)聲明。

那個...是什么?

沒錯,那就是可變參數(shù)的標(biāo)志。

但是那些參數(shù)叫什么呢?

沒有名字。

那怎么讀取它們呢?

va_list。

2)va_list實現(xiàn)可變參數(shù)

C/C++函數(shù)可以通過在其普通參數(shù)后添加逗號和三個點(,...)的方式來接受數(shù)量不定的附加參數(shù),而無需相應(yīng)的參數(shù)聲明。

特別的,雖然直接使用三個點作為函數(shù)的參數(shù)是合法的,但是這些參數(shù)并不能被讀?。ê竺鏁f明原因)。不推薦使用這樣的函數(shù)。

注意三個點必須加在參數(shù)列表的最后。

例如:

void print(int count,...);//合法
void print(...);//合法

void print(int count,...,int count2);//非法,CE

C/C++頭文件<stdarg.h><cstdarg>提供了對可變參數(shù)的支持。va_list是一個類型,定義在頭文件<stdarg.h><cstdarg>中。<stdarg.h>中定義了3個宏,與va_list配套使用,分別是va_list,va_start,va_argva_end。在C++中,C++11標(biāo)準(zhǔn)又新增了宏va_copy。所以,va_listva_start,va_argva_end是C與C++通用的。

讓我們看一個使用va_list實現(xiàn)可變參數(shù)的例子:

#include <iostream>
#include <cstdarg>
using namespace std;

void printint(int count,...){
    va_list a; 
    va_start(a,count);
    for(int i=1;i<=count;i++){
        int b=va_arg(a,int);
        cout<<b<<' ';
    }
    va_end(a);
}
int main(){
    printint(5,114514,233,-3456789,4,5);
    return 0;
}

這個函數(shù)從參數(shù)讀取數(shù)量等同于參數(shù)count的整數(shù)并輸出這個程序?qū)敵?code>114514 233 -3456789 4 5。

  1. 在函數(shù)體內(nèi),我們首先定義了一個va_list類型的a。a即是printint()的參數(shù)表,即其包含了包括count在內(nèi)的所有參數(shù)。你可以把va_list看做一個棧(實際上也是這么存儲的),參數(shù)從右至左入棧。

  2. 然后,我們調(diào)用了va_start宏。va_start宏有兩個參數(shù),第一個需要寫我們之前創(chuàng)建的va_list的名字(在這個例子里是a),第二個參數(shù)則寫...之前的上一個參數(shù)(在這個例子里是count)。你可以想象有一個指針,一開始什么都不指向(即指向NULL),我們調(diào)用va_start宏,則這個指針就指向了棧頂,并且一直彈棧,直到指針指向了...之前的上一個參數(shù)(在這個例子里是count)的位置。

  3. 然后,我們使用va_arg宏讀取函數(shù)的參數(shù)。va_arg宏有兩個參數(shù),第一個需要寫我們之前創(chuàng)建的va_list的名字,第二個填寫當(dāng)前所要讀取的參數(shù)的類型(這也是scanf為什么有那么多占位符的原因,因為它必須獲取參數(shù)的類型)。你可以認(rèn)為這個宏先進行彈棧,然后讀取棧頂內(nèi)容并返回。注意,va_arg的第二個參數(shù)中,char,char_16t,wchar_t,short以及其signed,unsigned版本要寫成int,float要寫成double,char_32t要寫成unsigned long。原因是C/C++的默認(rèn)類型轉(zhuǎn)換。否則必定RE。這個東西gcc會給出warning。除此之外,如果第二個參數(shù)的實際類型不同于va_arg的第二個參數(shù),這個參數(shù)會被吃掉。

  4. 最后,使用va_end宏結(jié)束函數(shù)參數(shù)的讀取。你可以認(rèn)為這個宏使指針重新指向NULL,并且刪除賦予va_list的內(nèi)存,使可變參數(shù)函數(shù)能正確返回。有助于代碼的健壯。

同時這個函數(shù)也可以使用while完成:

#include <iostream>
#include <cstdarg>
using namespace std;

void printint(int first,...){
    va_list a; 
    int b=first;
    va_start(a,first);
    while(b!=-1){
        cout<<b<<' ';
        b=va_arg(a,int);
    }
    va_end(a);
}
int main(){
    printint(114514,233,-3456789,4,5,-1);
    return 0;
}

實際上,va_list的優(yōu)點在于其可以接受不同類型的參數(shù),前提是你知道這些參數(shù)的類型。下面是一個例子,是本人實現(xiàn)的printf,只實現(xiàn)了%d%c

#include <iostream>
#include <cstdarg>
using namespace std;

void myprintf(string f,...){
    va_list a; 
    va_start(a,f);
    for(int i=0;i<f.size();i++){
        if(f[i]=='%'){
            i++;
            if(f[i]=='d'){;
                cout<<va_arg(a,int);
            }
            if(f[i]=='c'){
                cout<<char(va_arg(a,int));
            }
        }
        else cout<<f[i];
    }
    va_end(a);
}
int main(){
    myprintf('test\n%d%c',114514,'A');
    return 0;
}

這東西也可以有模板。但是由于類型轉(zhuǎn)換方面的問題,要想支持int以下的類型,恐怕是得寫一大堆的特化了。(想一想,怎么寫)由于作者太懶,就不寫了

那么va_copy呢?

實際上,如果你定義多個va_list,只有你定義的第一個va_list里面有那些參數(shù)的數(shù)據(jù)。va_copy有兩個參數(shù),類型都是va_list,用途是把第二個va_list的數(shù)據(jù)拷貝進第一個va_list里。

如果參數(shù)類型都相同的話誰會用這東西呢?還不如把這些參數(shù)拷貝進數(shù)組里(滑稽)所以這個東西用來重復(fù)處理具有多個數(shù)據(jù)類型的可變參數(shù)時會很方便。比如下面這個輸出兩遍的printf

#include <iostream>
#include <cstdarg>
using namespace std;

void myprintf(string f,...){
    va_list a,b; 
    va_copy(b,a);
    va_start(a,f);
    for(int i=0;i<f.size();i++){
        if(f[i]=='%'){
            i++;
            if(f[i]=='d'){;
                cout<<va_arg(a,int);
            }
            if(f[i]=='c'){
                cout<<char(va_arg(a,int));
            }
        }
        else cout<<f[i];
    }
    va_end(a);
    va_start(b,f);
    for(int i=0;i<f.size();i++){
        if(f[i]=='%'){
            i++;
            if(f[i]=='d'){;
                cout<<va_arg(b,int);
            }
            if(f[i]=='c'){
                cout<<char(va_arg(b,int));
            }
        }
        else cout<<f[i];
    }
    va_end(b);
}
int main(){
    myprintf('test\n%d%c',114514,'A');
    return 0;
}

3)其他

之前我們已經(jīng)知道了,直接使用三個點作為函數(shù)的參數(shù)是合法的,但是這些參數(shù)并不能被讀取。為什么呢?因為如果有一個直接使用三個點作為參數(shù)的函數(shù),則沒有...之前的上一個參數(shù),va_start將無法使用。然而親測不使用va_start會RE,所以這種函數(shù)的參數(shù)并不能被讀取。

之前我們已經(jīng)知道了,va_arg的第二個參數(shù)中,char,char_16t,wchar_t,short以及其signed,unsigned版本要寫成int,float要寫成doublechar_32t要寫成unsigned long。原因是C/C++的默認(rèn)類型轉(zhuǎn)換。否則必定RE。這個東西gcc會給出warning。除此之外,如果第二個參數(shù)的實際類型不同于va_arg的第二個參數(shù),這個參數(shù)會被吃掉。然而,寫成intva_arg讀取的也是int。比如下面的手寫printf的錯誤例子,不會輸出char類型的A,而會輸出int類型的65A的ASCII碼)。 所以想出默認(rèn)類型轉(zhuǎn)換這個餿主意的人真是個大銻。

#include <iostream>
#include <cstdarg>
using namespace std;

void myprintf(string f,...){
    va_list a; 
    va_start(a,f);
    for(int i=0;i<f.size();i++){
        if(f[i]=='%'){
            i++;
            if(f[i]=='d'){;
                cout<<va_arg(a,int);
            }
            if(f[i]=='c'){
                cout<<va_arg(a,int);
                //沒有轉(zhuǎn)換,正確寫法實際上是用強制類型轉(zhuǎn)換
                //cout<<char(va_arg(a,int));
            }
        }
        else cout<<f[i];
    }
    va_end(a);
}
int main(){
    myprintf('%c','A');
    return 0;
}

所以,如果有使用更低等的類型(比如char)的必要,使用強制類型轉(zhuǎn)換。

在函數(shù)最后,一定要調(diào)用va_end。

不要越界!不要越界?。〔灰浇纾。?!

你們的程序里有千萬塊內(nèi)存。只要不越界,這個系統(tǒng)就無法檢測到非法讀寫。

如果越界,非法讀寫將被檢測到,系統(tǒng)的保護模塊將會觸發(fā),你們的程序?qū)E!

不要越界!不要越界??!不要越界?。?!

然后就沒了。

5.參考資料

http://www.cplusplus.com/

C++ Primer

va_start和va_end使用詳解

C可變參數(shù)的實現(xiàn)


洛谷日報接受投稿,采用后有薄禮奉送,請戳 

https://www.luogu.org/discuss/show/47327 .
本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
C和指針之函數(shù)之求參數(shù)列表中的最大值
av_list
C |可變參數(shù)函數(shù)的實現(xiàn)及其不安全性
va_list、va_start、va_arg、va_end
C語言之可變參數(shù)問題 - 歡迎閣下光臨我的網(wǎng)絡(luò)日志-safeking‘s blog - 博...
變參函數(shù)的實現(xiàn)
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服