1. 程式人生 > >【C語言】可變引數列表剖析

【C語言】可變引數列表剖析

一、為什麼函式要有可變引數列表

      就舉一個簡單的例子來解答這個問題吧,具有一定C語言程式設計基礎的讀者,一定知道求兩個數平均值的函式,實現過程很簡單,我們只需要把兩個引數傳給函式,並用一個變數接收函式返回來的結果即可。

      但是,我們都知道現實生活中,我們需要求平均值的情況有很多種,比如,當我想要求某個同學期末平均成績,這個時候可能需要傳的引數個數就不是兩個了。

      那麼對於不同數量的引數,我們如何通過編寫一個函式來實現對不同數量的引數進行接收並處理呢?這就要用到可變引數列表了。

二、什麼是可變引數列表

      可變引數列表的實現是依靠標準庫中stdarg.h裡面定義的巨集來實現的。在stdarg.h標頭檔案中聲明瞭一個型別va_list和三個巨集——va_start, va_arg和va_end。 通過宣告va_list的變數,與這三個巨集的配合使用,可以訪問引數的值。

      其中,va_list的定義為:typedef char * va_list,下面是可變引數列表用到的三個巨集的定義:

#define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_end(ap)      ( ap = (va_list)0 )
#define va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )

其中用到的_INTSIZEOF巨集定義如下:

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

      依次對上面的巨集進行分析:

      (1)首先說一下_INTSIZEOF(n),怎麼理解這裡呢?首先,聯絡到前面的巨集定義 ,我們應該知道這裡的n就指的是可變引數的型別,假設在32位的程式中,當可變引數型別為char時,對於二進位制數(1+4-1) & ~(4-1)的結果為4,同樣的,在32位的程式中,如果可變引數型別是short(佔2個位元組),int(佔4個位元組),double(佔8個位元組), long double(佔12個位元組)時,它的結果分別是4,4,8, 12。所以_INTSIZEOF(n)的功能就是取整到sizeof(int)。怎麼理解取整到n呢?比如n為4,1,2,3,4取4,5,6,7,8取8。

      (2)va_start(ap,v)中 ap = (va_list)&v + _INTSIZEOF(v),其中va_list是一個char *型別,類比於學習過的內容,就是取出v的地址中的第一個位元組,然後加上_INTSIZEOF(v),並把得到的結果賦給ap。

      (3)va_arg(ap,t),從ap中取出型別為t的資料,並將指標相應後移。如va_arg(ap, int)就表示取出一個int資料並將指標向移四個位元組。

      va_end(ap)很簡單,就是將ap指標置成空指標。

      因此在函式的整個過程就是:先用va_start()得到變參的起始地址,再用va_arg()一個一個取值,最後再用va_end()將指標置為空,說明引數獲取完畢,接下來就輪到解析可變引數了。

三、可變引數列表的使用

      需要注意一點,在C和C++中無法確定傳入的可變引數的個數,所以一般情況下,在使用可變引數列表進行傳參時,需要先指定傳可變引數的個數。下面的程式碼中在主函式對Avg函式傳參時,第一個引數4就說明了,要傳入的可變引數的數量為4。還有一點需要說明一下,printf是根據"%"的個數來確定輸入引數的個數的。

#include <stdio.h>
#include <stdarg.h>

float Avg(int n, ...)
{
	va_list arg;
	int i = 0;
	float sum = 0;

	va_start(arg, n);

	for (i = 0; i < n; ++i)
	{
		sum += va_arg(arg, int);
	}

	va_end(arg);

	return sum / n;
}

int main()
{
    printf("%.2f", Avg(4, 23, 43, 23, 12));
    
    return 0;
}

四、可變引數的限制

      (1)可變引數必須從頭開始按順序訪問,不可以一開始就訪問引數列表中間的值;

      (2)引數列表至少包含一個命名引數。如果一個命名引數都沒有,無法使用va_start;

      (3)va_start, va_arg, va_end巨集是無法確定實際存在的引數數量以及引數的型別。