天天看點

C語言的變參函數設計

 在C語言中,函數參數的傳遞方式有值傳和址傳.值傳是把實參的一個專用的、臨時的複制值給被調函數中相應的形參被調用函數使用、修改這個傳來的複制值,不會影響實參的值.址傳則是把變量(實參)的位址傳給被調函數.被調函數通過這個位址找到該變量的存放位置,直接對該位址中存放的變量的内容進行存取操作.是以,在被調用函數中可以修改實參的值.這也是函數參數址傳的優點.無論是值傳還是址傳,都要求實參的數目及類型與形參要完全一緻.在一般的程式設計語言中,函數參數的數目及類型是不可變的.即函數被設計之後,隻能接收已固定個數和固定類型的實參.這樣在編譯時,函數形參的存儲空間便于确定.但是在C語言中,不但參數的類型可變,參數的個數也是可變的.也就是說,在形參表中可以不明确指定傳遞參數的個數和類型,一個常見的庫函數Printf() 就是如此.這種函數稱之為可變長參數函數(變參函數).可變長參數函數的參數數目和類型雖然是可變,但其設計原理與固定參數函數的設計原理是一緻的,必須有辦法告訴變參函數沒有指定的參數的個數和類型。下面我們通過對可變長參數函數的了解和設計,在教學中更有助于加深掌握C語言函數設計的思想方法.利用其它語言所不具有的這一可變長參數功能,可以開發靈活、友善、簡潔、功能強的程式子產品.1,可變長參數函數的設計方法

  在标準檔案stdarg.h中包含帶參數的宏定義

typedef void *va_list

  #define va_arg(ap,type) (*((type *)(ap))++)

  #define va_start(ap,lastfix) (ap=…)

  #define va_end(ap)

(1) 可變長參數函數用規定格式定義為“類型函數名(firstfix,…,lastfix,…)”.firstfix,…,lastfix表示函數參數清單中的第一個和最後一個固定參數,該參數清單中至少要有一個固定參數,其作用是為了給變參函數确定清單中參數的個數和參數的類型.

(2) 指針類型va_list用來說明一個變量ap(argument pointer——可變參數指針),此變量将依次引用可變參數清單中用省略号“…”代替的每一個參數.即指向将要操作的變參.

(3) 宏va_start (ap,lastfix)是為了初始化變參指針ap,以指向可變參數清單中未命名的第一個參數,即指向lastfix後的第一個變參.它必須在指針使用之前調用一次該宏,參數清單中至少有一個未命名的可變參數.從宏定義可知其正确性.

(4) 宏va_arg (ap,type)調用,将ap指向下一個可變參數,而ap的類型由type确定,type資料類型不使用float類型.調用後将新的變參可指向一個工作變參,如iap=va_start (ap,int)調用.

(5) 宏va_end (ap)從stdarg.h中看出定義為空,即未定義.其功能完成清除變量ap的作用,表明程式以後不再使用,若該指針變量需再使用,必須重新調用宏va_start 以啟動該變量.

2,應用舉例

  利用上面讨論的一般可變長參數函數的設計方法,通過執行個體逐漸分析其特點,以加深函數實參與形參一緻性的了解.

2.1 變參類型相同的函數

#include <stdio.h>

#include <stdarg.h>

int mul(int num,int data1,)

{

    int total = data1;

    int arg,i;

    va_list ap;

    va_start(ap,data1);

    for(i=1;i<num;i++)

    {

        arg = va_arg(ap,int);

        total*=arg;

    }

    va_end(ap);

    return total;

}

long mul2(int i,)

  int *p,j;

  p = &i+1;//p指向參數清單下一個位置

  long s = *p;

  for (j=1;j<i;j++)

    s *= p[j];

   return s;

int main()

    printf("%d\n",mul(3,2,3,5));

    printf("%d\n",mul2(3,2,3,5));

    return 0;

    在該例中,for{…}循環中的ap指向的下一個變參類型皆為整型,是以變參類型相同,但變參個數不定.

2.2可變參數在編譯器中的處理 

    我們知道va_start,va_arg,va_end是在stdarg.h中被定義成宏的, 由于1)硬體平台的不同 2)編譯器的不同,是以定義的宏也有所不同,下面以VC++中stdarg.h裡x86平台的宏定義摘錄如下(’"’号表示折行): 

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) \ 

( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) 

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

    定義_INTSIZEOF(n)主要是為了某些需要記憶體的對齊的系統.C語言的函數是從右向左壓入堆棧的,圖(1)是函數的參數在堆棧中的分布位置.我們看到va_list被定義成char*,有一些平台或作業系統定義為void*.再看va_start的定義,定義為&v+_INTSIZEOF(v),而&v是固定參數在堆棧的位址,是以我們運作va_start(ap, v)以後,ap指向第一個可變參數在堆棧的位址,如圖: 

高位址|-----------------------------| 

|函數傳回位址 | 

|-----------------------------| 

|. | 

|第n個參數(第一個可變參數) | 

|-----------------------------|<--va_start後ap指向 

|第n-1個參數(最後一個固定參數)| 

低位址|-----------------------------|<-- &v 

圖( 1 ) 

    然後,我們用va_arg()取得類型t的可變參數值,以上例為int型為例,我們看一下va_arg取int型的傳回值: j= ( *(int*)((ap += _INTSIZEOF(int))-_INTSIZEOF(int)) ); 

首先ap+=sizeof(int),已經指向下一個參數的位址了.然後傳回ap-sizeof(int)的int*指針,這正是第一個可變參數在堆棧裡的位址(圖2).然後用*取得這個位址的内容(參數值)賦給j. 

|-----------------------------|<--va_arg後ap指向 

圖( 2 ) 

    最後要說的是va_end宏的意思,x86平台定義為ap=(char*)0;使ap不再指向堆棧,而是跟NULL一樣.有些直接定義為((void*)0),這樣編譯器不會為va_end産生代碼,例如gcc在linux的x86平台就是這樣定義的.在這裡大家要注意一個問題:由于參數的位址用于va_start宏,是以參數不能聲明為寄存器變量或作為函數或數組類型.關于va_start, va_arg, va_end的描述就是這些了,我們要注意的是不同的作業系統和硬體平台的定義有些不同,但原理卻是相似的.

#include "stdio.h"

#include "stdlib.h"

void myprintf(char* fmt, )        //一個簡單的類似于printf的實作,//參數必須都是int 類型

    //char* pArg=NULL;               //等價于原來的va_list

    va_list pArg;

    char c;

   // pArg = (char*) &fmt;          //注意不要寫成p = fmt !!因為這裡要對參數取址,而不是取值

  // pArg += sizeof(fmt);         //等價于原來的va_start         

    va_start(pArg,fmt);

    do

        c =*fmt;

        if (c != '%')

        {

            putchar(c);            //照原樣輸出字元

        }

        else

        {//按格式字元輸出資料

            switch(*++fmt)

            {

            case 'd':

                printf("%d",*((int*)pArg));           

                break;

            case 'x':

                printf("%#x",*((int*)pArg));

            case 'f':

                printf("%f",*((float*)pArg));

            default:

            }

            //pArg += sizeof(int);               //等價于原來的va_arg

            va_arg(pArg,int);

        ++fmt;

    }while (*fmt != '\0');

    //pArg = NULL;                               //等價于va_end

    va_end(pArg);

    return;

int main(int argc, char* argv[])

    int i = 1234;

    int j = 5678;

    myprintf("the first test:i=%d",i,j);

    myprintf("the secend test:i=%f; %x;j=%d;",i,0xabcd,j);

    system("pause");

本文轉自Phinecos(洞庭散人)部落格園部落格,原文連結:http://www.cnblogs.com/phinecos/archive/2007/08/24/868524.html,如需轉載請自行聯系原作者

繼續閱讀