C函式可變引數的運用 三個點

2021-06-20 04:57:09 字數 2668 閱讀 7960

c++中有函式過載這種方法,以供我們呼叫時要可以不確定實參的個數,其實 c 語言也可以,而且更高明!

我們在stdio.h 中可以看到 printf() 函式的原型:

int printf(char * format,...)

事實上,我們如果要寫這樣的函式也可以類似的寫,那麼在定義函式時用上這個符號「 ... 」 ,它叫佔位符,喊它 「 三個點 」 也可以,只要你願意!那麼我可以這樣定義我的函式:

fun(int a,...)

只要上課認真聽了的同學(傻瓜除外)都知道,這是個空函式,它是什麼都不做的,光這樣寫還不行的,具體應該怎樣定義呢?

且聽我介紹3 個小東東:

1、 va_list

2、 va_arg()

3、 va_start()

在學習這3 個小東東之前,我們先回憶一下, c 語言是怎麼操作檔案時,是怎麼樣處理記憶體中的資料的呢?學習檔案操作時,我們提到了「流」的概念,我們用指標指向資料所在的記憶體位址,再乙個乙個的操作。

學習指標時,我們知道有函式指標這個東東,不是指標函式而是函式打針哦!(呵呵,我的同學如果還記得就當複習一下,不要嫌我囉嗦^_^ )。我們記得程式在執行時,會將函式儲存到記憶體中去。現在深入的講一點點,儲存函式時,引數傳遞的過程是怎樣實現的呢?所謂的形式引數(區域性變數)實質上又是什麼呢?把這些問題連起來想想,想通了,你的思維勢如破竹!

在呼叫函式時,程式同樣會把實參傳入,在函式儲存區儲存起來,如果有很多引數,將一起儲存起來。

這時候就要用到va_list 了,這是個型別定義,我們可以把它理解成乙個指標,它指向第乙個引數的位址。

如果,我們這樣定義: va_list pp ;

則pp 就是這樣一種變數,它是指向所有引數中的第乙個引數的。它不同於一般的指標變數,它是個復合變數,什麼是復合變數啊?結構體型別的嘛,呵呵。如果 a 是第乙個引數,能不能寫成 pp=a 呢?

假設我定義了char d="ruixin",e="gelin"; 我要把 e 的值賦給 d ,能不能寫成 d=e 呢?得用 strcpy() ,是吧!呵呵,一樣的道理,這兒我們也用乙個函式來實現,它就是 va_start();

如果這樣寫:va_start(pp,a);

那麼pp 就指向第乙個引數 a 了,並且可得到 a 的型別 int 。

這時候如果有下乙個引數,就需要使pp 指向下乙個引數,並且得到它的型別。同樣需要使用函式來實現,這個函式是: va_arg()

可以這樣寫:va_arg(pp, 型別 ) ,這樣 pp 就指向乙個引數,並且可以得到那個引數的型別了。

注意!型別非常重要,學過指標的都應該清楚,指標的型別如果弄錯的話,位置正確,取出來的數可能也是亂七八糟的。

下面我們看乙個簡單的例子:

#include

#include

void fun(int a,...)

while (a!=0);//直到引數為 0 時停止迴圈

} main()

注意!

一定要有上面兩個檔案包含命令,因為程式中用到的那3個小東東都在那個檔案裡。其實真正意義上應該說那是函式,實質上那不過是兩個巨集,呵呵。

什麼是巨集,什麼是函式,不是這兒要講的,也和這沒太大關係。寫出來,僅僅是回答乙個為什麼……

va_list 是在c語言中解決變參問題的一組巨集

va_list的用法:     

(1)首先在函式裡定義一具va_list型的變數,這個變數是指向引數的指標

(2)然後用va_start巨集初始化變數剛定義的va_list變數,這個巨集的第二個引數是第乙個可變引數的前乙個引數,是乙個固定的引數。

(3)然後用va_arg返回可變的引數,va_arg的第二個引數是你要返回的引數的型別。

(4)最後用va_end巨集結束可變引數的獲取。然後你就可以在函式裡使用第二個引數了。如果函式有多個可變引數的,依次呼叫va_arg獲取各個引數。

va_list在編譯器中的處理:

1)在執行va_start(ap,v)以後,ap指向第乙個可變引數在堆疊的位址。

(2)va_arg()取得型別t的可變引數值,在這步操作中首先apt = sizeof(t型別),讓ap指向下乙個引數的位址。然後返回ap-sizeof(t型別)的t型別*指標,這正是第乙個可變引數在堆疊裡的位址。然後用*取得這個位址的內容。

(3)va_end(),x86平台定義為ap = ((char*)0),使ap不再指向堆疊,而是跟null一樣,有些直接定義為((void*)0),這樣編譯器不會為va_end產生**,例如gcc在linux的x86平台就是這樣定義的。

要注意的是:由於引數的位址用於va_start巨集,所以引數不能宣告為暫存器變數,或作為函式或陣列型別。

使用va_list應該注意的問題:

(1)因為va_start, va_arg, va_end等定義成巨集,所以它顯得很愚蠢,可變引數的型別和個數完全在該函式中由程式**控制,它並不能智慧型地識別不同引數的個數和型別. 也就是說,你想實現智慧型識別可變引數的話是要通過在自己的程式裡作判斷來實現的.

(2)另外有乙個問題,因為編譯器對可變引數的函式的原型檢查不夠嚴格,對程式設計查錯不利.不利於我們寫出高質量的**。

小結:可變引數的函式原理其實很簡單,而 va系列是以巨集定義來定義的,實現跟堆疊相關。我們寫乙個可變函式的c函式時,有利也有弊,所以在不必要的 場合,我們無需用到可變引數,如果在c++裡,我們應該利用c++多型性來實現可變引數的功能,盡量避免用c語言的方式來實現。

三個點擬合圓形的函式C

函式說明 public void fitcirclefromthreepoints double 點1x,double 點1y,double 點2x,double 點2y,double 點3x,double 點3y,outdouble 圓心x座標,outdouble 圓心y座標,outdouble ...

main函式的三個引數

我們平時寫程式時main函式是省略引數的,或者是省略部分引數,其實main函式是有三個引數的。int main int argc,char ar const char envp argc int 型別,用於存放命令列引數的個數 包括函式名 ar char陣列型,每個元素都是乙個字元指標,指向乙個字串...

C 三個點用法

c語言中實現引數個數可變的函式 採用c語言程式設計的時候,函式中形式引數的數目通常是確定的,在呼叫時要依次給出與形式引數對應的所有實際引數。但在某些情況下希望函式的引數個數可以根據需要確定。典型的例子有大家熟悉的函式printf scanf 和系統呼叫execl 等 1 在win環境下程式設計例子 ...