有很多同學對我們經常使用的函數printf和scanf都不以為然,其實這兩個函數在c語言當中還是比較典型的。一般我們編程的時候,函數中形式參數的數目通常是確定的,在調用時要依次給出與形式參數對應的實際參數。但在某些情況下我們希望函數的參數個數可以根據需要確定,因此c語言引入可變參數函數。典型的可變參數函數的例子有printf()、scanf()。
下面我們就說一下這些函數的實現。
這些不定參數函數在c語言中是通過堆棧的方式實現的,這樣看來理論上好像參數的個數是沒有限制的實際上由于內存或者棧的大小的限制,實際上參數的個數是有上限的,只是這個上限我們一般情況下不會達到。如果達到了也會出現錯誤。
在c語言中,不定參函數的具體實現主要通過c語言定義的幾個函數宏來完成,具體內部空間如何分配,如何調用均在其中,這也為我們自己完成一些不定餐函數提供了便利條件。下面介紹下這些函數宏。
VA_LIST 是在C語言中解決變參問題的一組宏,所在頭文件:#include <stdarg.h>
INTSIZEOF 宏,獲取類型占用的空間長度,小占用長度為int的整數倍:
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
VA_START宏,獲取可變參數列表的第一個參數的地址(ap是類型為va_list的指針,v是可變參數左邊的參數):
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
VA_ARG宏,獲取可變參數的當前參數,返回指定類型并將指針指向下一參數(t參數描述了當前參數的類型):
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
VA_END宏,清空va_list可變參數列表:
#define va_end(ap) ( ap = (va_list)0 )
具體用法:
(1)首先在函數里定義一具VA_LIST型的變量,這個變量是指向參數的指針;
(2)然后用VA_START宏初始化剛定義的VA_LIST變量;
(3)然后用VA_ARG返回可變的參數,VA_ARG的第二個參數是你要返回的參數的類型(如果函數有多個可變參數的,依次調用VA_ARG獲取各個參數);
(4)后用VA_END宏結束可變參數的獲取。
Ok下面舉個例子來說明下:
void arg_test(int i, ...);
int main(int argc,char *argv[])
{
int int_size = _INTSIZEOF(int);
printf("int_size=%d\n", int_size);
arg_test(0, 4);
//arg_cnt(4,1,2,3,4);
return 0;
}
void arg_test(int i, ...)
{
int j=0;
va_list arg_ptr;
va_start(arg_ptr, i);
printf("&i = %p\n", &i);//打印參數i在堆棧中的地址
printf("arg_ptr = %p\n", arg_ptr);//打印va_start之后arg_ptr地址
/*這時arg_ptr應該比參數i的地址高sizeof(int)個字節,即指向下一個參數的地址*/
j=*((int *)arg_ptr);
printf("%d %d\n", i, j);
j=va_arg(arg_ptr, int);
printf("arg_ptr = %p\n", arg_ptr);//打印va_arg后arg_ptr的地址
/*這時arg_ptr應該比參數i的地址高sizeof(int)個字節,即指向下一個參數的地址,如果已經是后一個參數,arg_ptr會為NULL*/
va_end(arg_ptr);
printf("%d %d\n", i, j);
}
說明:
int int_size = _INTSIZEOF(int);得到int類型所占字節數
va_start(arg_ptr, i); 得到第一個可變參數地址
根據定義(va_list)&v得到起始參數的地址, 再加上_INTSIZEOF(v) ,就是其實參數下一個參數的地址,即第一個可變參數地址。
j=va_arg(arg_ptr, int); 得到第一個可變參數的值,并且arg_ptr指針上移一個_INTSIZEOF(int),即指向下一個可變參數的地址。
va_end(arg_ptr);置空arg_ptr,即arg_ptr=(void *)0;
在這里還要說一下,不定參數在c語言中的函數原型寫法參數部分是“...”三個點。
后附上一個我們自己寫的簡單的printf的代碼:
#include <stdarg.h>
void my_printf(const char* fmt, ... )
{
va_list ap;
va_start(ap,fmt); /* 用后一個具有參數的類型的參數去初始化ap */
for (;*fmt;++fmt)
{
/* 如果不是控制字符 */
if (*fmt!='%')
{
putchar(*fmt); /* 直接輸出 */
continue;
}
/* 如果是控制字符,查看下一字符 */
++fmt;
if ('\0'==*fmt) /* 如果是結束符 */
{
assert(0); /* 這是一個錯誤 */
break;
}
switch (*fmt)
{
case '%': /* 連續2個'%'輸出1個'%' */
putchar('%');
break;
case 'd': /* 按照int輸出 */
{
/* 下一個參數是int,取出 */
int i = va_arg(ap,int);
printf("%d",i);
}
break;
case 'c': /* 按照字符輸出 */
{
/** 但是,下一個參數是char嗎*/
/* 可以這樣取出嗎? */
char c = va_arg(ap,char);
printf("%c",c);
}
break;
}
}
}
va_end(ap); /* 釋放ap—— 必須! 見相關鏈接*/
}
看完后,你是不是自己也想試一下實現一個不定參函數呢。