一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - C/C++ - C++可變參數的實現方法

C++可變參數的實現方法

2020-11-18 11:02C++教程網 C/C++

可變參數給編程帶來了很大的方便,在享受它帶來的方便的同時,很有必要了解一下其實現方式,在了解編程語言的同時,也可以擴展編程的思路。

可變參數的實現要解決三個問題:

1.如何調用帶有可變參數的函數
2.如何編譯有可變參數的程序
3.在帶有可變參數的函數體中如何持有可變參數
第一個問題, 調用時在可以傳入可變參數的地方傳入可變參數即可,當然,還有一些需要注意的地方,后面會提到。

第二個問題,編譯器需要在編譯時采用一種寬松的檢查方案,,這會帶來一些問題, 比如對編程查錯不利。

第三個是我在這里要關心的問題,先以C語言為例分析其實現原理。

printf和scanf是C語言標準庫中最常見的可變參數函數, printf的簽名是

 

復制代碼 代碼如下:

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

 

其中,... 表示可變參數,現在模仿printf寫一個簡單的例子。

一、一個簡單了例子:

 

復制代碼 代碼如下:


#include <windows.h>
#include <stdio.h>

 

void VariableArgumentMethod(int argc, ...);

int main(){
    VariableArgumentMethod(6, 4, 7, 3, 0, 7, 9);
    return 0;
}

void VariableArgumentMethod(int argc, ...){
    // 聲明一個指針, 用于持有可變參數
    va_list pArg;
    // 將 pArg 初始化為指向第一個參數
    va_start(pArg, argc);
    // 輸出參數
  for(int i = 0; i != argc; ++i){
        // 獲取 pArg 所指向的參數并輸出
        printf("%d, ", va_arg(pArg, int) );
    }

    va_end(pArg);
}


void VariableArgumentMethod(int argc, ...)是一個可變參數函數,這個函數用于將 argc 指定個數的可變參數輸出。
VariableArgumentMethod(6, 4, 7, 3, 0, 7, 9); 是對這個函數的調用,第一個實參 6 表示后面跟了 6 個參數。

 

在 VariableArgumentMethod 的函數體中:

1. va_list pArg;

定義了一個用于持有可變參數的指針,通過將這個指針在傳入的可變參數表中移動,可以持有第一個可變參數。

2. va_start(pArg, argc);

讓 pArg 指向可變參數列表中的第一個參數。argc 是一個用來定位的參數,因為可變參數是從 argc 后開始的,后面會說明為什么要這樣定位。

3. va_arg(pArg, int);

這句話放在循環體中,用于取出可變參數表中的參數。并且,它會讓 pArg 移向下個可變參數(如果已經到達末尾,則它將指向一個沒有意義的地址)。

4. va_end(pArg);

給 pArg 清零,個人認為在這里可有可無,因為 pArg 已經不需要了。

 

就這樣,VariableArgumentMethod 函數體遍歷了可變參數表中傳入的參數,并用printf("%d, ", va_arg(pArg, int) ) 進行了輸出。

二、實現細節

1. 先了解一下編譯器如何處理傳遞參數這個問題的。

編譯器是將參數壓入棧中進行傳遞的。傳遞實參的時候,編譯器會從實參列表中,按從右到左的順序將參數入棧,對于 VariableArgumentMethod(6, 4, 7, 3, 0, 7, 9)調用,則入棧的順序是 9, 7, 0, 3, 7, 4, 6 (注意沒有可變參數與不可變參數之分)。由于棧的地址是從高到低的,所以實參入棧后,實參在棧中的分布如下圖。可以看出,實參在棧中,還是保持了左邊參數處于低地址,右邊參數處于高地址的狀態。OK,知道這些就夠了。

低地址                            高地址

 

...

6

4

7

3

0

7

9

...

 

 

2. va_list, va_start, va_arg 和 va_end

va_list 是一個定義的指針類型,va_start, va_arg 和 va_end 都是C語言用于處理可變參數而定義的宏,在stdarg.h文件中。由于硬件平臺的不同,編譯器的不同,導致它們的定義也有所不同,但基本思路相同。以下是相關宏的定義。

 

復制代碼 代碼如下:


typedef char *  va_list;

 

#define _ADDRESSOF(v)   ( &(v) )

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

#define va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )

#define va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )

#define va_end(ap) ( ap = (va_list)0 )

 

可以看出,此處引入了另外兩個宏 _ADDRESSOF 和 _INTSIZEOF。

_ADDRESSOF(v) 是用于獲取變量地址的,這一眼就能看出來;

_INTSIZEOF(n) 是用于對齊的。(什么是對齊呢?這是因為棧的結構導致的,在 32 位機中,棧中每個單元都是占 4 個字節的,這往往是一個 int 型的長度,但實際傳過來的參數可能并不正好是 4 個字節,或者正好是 4 的倍數個字節,就好像坐車時不會賣半個座位給乘客一樣,如果傳入的數據沒有正好占 4 個或 4 的倍數個字節,則需要對齊(補齊)。至于為什么這個表達式能夠對齊,需要分析一下);

va_start(ap,v) 中,ap 是用于持有可變參數的指針, v 是最后一個非可變參數的參數,(va_list)_ADDRESSOF(v) 獲取 v 的地址,并轉為 va_list 類型的,v 是最后一個非可變參數的參數,在本例中應為 6, 在上圖中處理棧的低地址端,_INTSIZEOF(v) 獲取了一個對齊地址,這里應為 4, 兩個相加后,即指向了第一個可變參數,即上圖中的 4, 將這個值賦給 ap 后,就讓 ap 指向了第一個可變參數。(從這里可以看出,將va_list 定義為 char* 是很有用的,因為 char 長度為一個字節,便于指針運算);

 va_arg(ap,t) 中,ap 是用于持有可變參數的指針,t 是要獲取參數的類型,ap += _INTSIZEOF(t) 讓 ap 指向下一個參數,但是,此處還需要獲取當前參數的值,所以又將表達式減回來,返回的應是一個 va_list(char*) 型的指針,因此要轉型為 t* 后再進行解引用運算,得到當前參數的值。(注意這里有個將 ap 移向下一個參數又減回來的操作,本人感覺不太好,一方面這里有個浪費的操作,對性能會有一些影響,另一方面,我更希望將取當前值的操作和移向下一個的操作分離,這樣可以讓程序員有更多的控制,并且容易理解。)

va_end(ap) 則是讓 ap 指向一個空地址。

通過以上分析,可以發現,C 語言中可變參數是從棧中按順序訪問的,過程中所使用的三個宏,也只是對操作的簡單包裝,完全可以自己編程實現。而且,參數的類型和個數是不能直接確定的,在本例中,VariableArgumentMethod 的第一個參數用于指定參數的個數,而參數的類型約定為整形,這樣程序才能正常運行,再說到 printf,它之所以能識別參數的個數,是因為它的第一個參數中必須要描述后面參數的格式字符串,這正是一開始所提到的第一個問題中說到的要注意的問題。這也是它被很多人所詬病的原因,但是,本人認為這種方式是很好的,后面會與 java 和 .net 的實現方式進行比較。

三、java 和 .net 實現可變參數的方式。

java 從1.5以后,開始支持可變參數,其定義語法為:

 

復制代碼 代碼如下:

void testMethod(String ... args)


對于這個方法,可以這樣調用:testMethod("gly", "zxy", "ChenFei");

 

.net 也支持可變參數,其定義語法為:

 

復制代碼 代碼如下:

void TestMethod(params string[] args)

 

對于這個方法,可以這樣調用:TestMethod("gly", "zxy", "ChenFei");

在 java 和 .net 中,對于可變參數的實現基本是一樣的:編譯器在編譯時,將方法簽名中的可變參數視為相應類型的數組,編譯相應的調用時,根據實參生成一個數組,將參數裝入到數組中進行傳遞,而在可變參數方法的方法體中,按使用數組的方式使用可變參數。

四、兩種實現方式的比較

C 語言的實現方式與 java .net 的實現方式相比,C 語言需要程序員做更多的工作,而且,確實增加了出錯的機會,java .net 的實現方式可以很容易的確定參數的類型和個數,這些 C 的實現中是沒有的,但是 java .net 的實現方式會生成臨時數組,當然 java .net 有垃圾回收機制,但是,垃圾什么時候被回收是不確定的,而且是代價很大的,垃圾回收是個好東西,但我不喜歡,我認為不需要的東西應該立即釋放,這是完美的一個方面的體現。C 中沒有這個問題,參數的個數和類型問題可以靠約定或指定來解決,而這兩個問題在 java 和 .net 中,參數個數其實是間接傳遞過去了(數組的長度),參數類型則是在方法簽名中約定了。當然,java .net 的設計目標和 C 語言不同,這里說多了。

延伸 · 閱讀

精彩推薦
  • C/C++C語言中炫酷的文件操作實例詳解

    C語言中炫酷的文件操作實例詳解

    內存中的數據都是暫時的,當程序結束時,它們都將丟失,為了永久性的保存大量的數據,C語言提供了對文件的操作,這篇文章主要給大家介紹了關于C語言中文件...

    針眼_6702022-01-24
  • C/C++學習C++編程的必備軟件

    學習C++編程的必備軟件

    本文給大家分享的是作者在學習使用C++進行編程的時候所用到的一些常用的軟件,這里推薦給大家...

    謝恩銘10102021-05-08
  • C/C++詳解c語言中的 strcpy和strncpy字符串函數使用

    詳解c語言中的 strcpy和strncpy字符串函數使用

    strcpy 和strcnpy函數是字符串復制函數。接下來通過本文給大家介紹c語言中的strcpy和strncpy字符串函數使用,感興趣的朋友跟隨小編要求看看吧...

    spring-go5642021-07-02
  • C/C++C++之重載 重定義與重寫用法詳解

    C++之重載 重定義與重寫用法詳解

    這篇文章主要介紹了C++之重載 重定義與重寫用法詳解,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下...

    青山的青6062022-01-04
  • C/C++深入理解goto語句的替代實現方式分析

    深入理解goto語句的替代實現方式分析

    本篇文章是對goto語句的替代實現方式進行了詳細的分析介紹,需要的朋友參考下...

    C語言教程網7342020-12-03
  • C/C++c++ 單線程實現同時監聽多個端口

    c++ 單線程實現同時監聽多個端口

    這篇文章主要介紹了c++ 單線程實現同時監聽多個端口的方法,幫助大家更好的理解和學習使用c++,感興趣的朋友可以了解下...

    源之緣11542021-10-27
  • C/C++C語言實現電腦關機程序

    C語言實現電腦關機程序

    這篇文章主要為大家詳細介紹了C語言實現電腦關機程序,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下...

    xiaocaidayong8482021-08-20
  • C/C++C/C++經典實例之模擬計算器示例代碼

    C/C++經典實例之模擬計算器示例代碼

    最近在看到的一個需求,本以為比較簡單,但花了不少時間,所以下面這篇文章主要給大家介紹了關于C/C++經典實例之模擬計算器的相關資料,文中通過示...

    jia150610152021-06-07
主站蜘蛛池模板: 成人久久伊人精品伊人 | 国产精品毛片久久久久久久 | 91影视在线看免费观看 | 亚洲第一男人天堂 | 暖暖视频高清图片免费完整版 | 天天色天天综合 | bl双性受乖调教改造身体 | 人与动人物人a级特片 | 搞逼综合网 | 网站久久 | 日韩一区二区在线视频 | 日韩欧美一区二区三区视频 | 国产一区二区三区高清视频 | 久久久久久免费高清电影 | 亚洲大片免费观看 | 色琪琪久久草在线视频 | 婷婷日日夜夜 | 亚洲黄视频在线观看 | 国产专区一va亚洲v天堂 | 乌克兰粉嫩摘花第一次 | 国产一区视频在线免费观看 | 久久91精品国产91久久户 | 美女视频一区二区三区在线 | 牛人国产偷窥女洗浴在线观看 | 近亲乱中文字幕 | 午夜在线观看免费完整直播网页 | 我的妹妹最近有点怪在线观看 | 日韩中文字幕视频在线观看 | 公翁的舌尖研磨她的花蒂小说 | 东京道一本热大交乱 | 97视频免费人人观看人人 | 色色色资源站 | 日本高清全集免费观看 | 午夜国产| 欧美日韩一区二区三区免费 | 国产精品欧美韩国日本久久 | 青春草视频免费观看 | 亚洲va精品中文字幕 | 欧美一区二区视频 | 男人把大ji巴放进女人小说 | 日本黄大片影院一区二区 |