1. 程式人生 > >實現簡單的printf函式

實現簡單的printf函式

首先,要介紹一下printf實現的原理

printf函式原型如下:
int printf(const char* format,...);

返回值是int,返回輸出的字元個數。

例如:
int main()
{
    int n;
    n=printf("hello world,%d\n",100);
    printf("返回值:%d\n",n);
    return 0;
}


測試結果:

hello world,100

返回值:16

測試結果是16,是因為100雖然是整型數,但是輸出時計算返回值它是3個字元。


引數format是一個字元指標,指向printf裡的第一個字串。

引數...是不定引數。這是printf能夠實現的核心。

接下來介紹一下不定引數是如何實現的。

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

函式的引數由右向左依次入棧,如下圖:



比如我們printf實際輸入的引數有4個,printf(char* format,arg1,arg2,arg3,arg4);

這些引數在記憶體中從低地址到高地址依次為format,arg1,arg2,arg3,arg4。

因為format是指標,所以所佔的位元組大小為一個int的大小。

所以如果我們找到format的儲存地址,從format首地址開始,加上一個int的大小,此時地址剛好就是引數arg1的首地址,然後再加上sizeof(arg1),此時地址又剛好是arg2的首地址,這樣我們就能依次找出引數所在地址。

具體實現時,我們只需要定義一個指標變數ap指向arg1引數的起始地址,同時分析format引數所指的字串,從字串第一個字元開始檢查,如果遇到%則通過分析%後面的字元就能判斷出變數的型別,此時輸出ap地址上所指向的變數的值,同時ap指標向右移動該變數型別大小位元組個單位,使ap指向下一個引數的儲存地址,然後再次分析字串,直到分析到字串結尾結束。

通過上面的引數入棧方式我們可以得到如下結論:


如果想將棧中的引數讀出來,我們只需要知道,棧頂元素的地址即第一個引數的地址即可。通過前面變參函式的分析,通過變參函式第一個引數可以知道傳遞的引數個數。

當然,每個引數都有自己的型別,還有的就是位元組對齊了。在讀取引數的時候,這些問題都必須考慮到。

實際上處理變參時,已經有封裝好的巨集處理這些所有問題

typedef   char  * va_list;                  //將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)




這些巨集在不同的作業系統,有不同的實現,想使用的話,只需要包含標頭檔案stdarg.h就可以了。

(1)va_start巨集的作用 :

printf(const char* format,arg1,agr2,....)

實現ap指向第一個實際引數arg1的地址,實際引數指第一個引數format後的第一個引數arg1。即va_start(ap,format)。

(2)va_arg巨集作用:

t指的是分析出來的實際引數的變數型別,首先ap向後移動sizeof(t)個單位,指向下一個實際引數的地址,同時返回(ap-sizeof(t))的地址,返回的地址跟剛開始時地址一樣。實際上就是為了ap移動到下一個引數的地址,為了下一次輸出。

(3)va_end巨集的作用

將ap指標賦值為NULL,即0

看一下實現程式碼:

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

void printch(const char ch)   //輸出字元
{  
    putchar(ch);  
}  


void printint(const int dec)     //輸出整型數
{  
    if(dec == 0)  
    {  
        return;  
    }  
    printint(dec / 10);  
    putchar((char)(dec % 10 + '0'));  
}  


void printstr(const char *ptr)        //輸出字串
{  
    while(*ptr)  
    {  
        putchar(*ptr);  
        ptr++;  
    }  
}  


void printfloat(const float flt)     //輸出浮點數,小數點第5位四捨五入
{  
    int tmpint = (int)flt;  
    int tmpflt = (int)(100000 * (flt - tmpint));  
    if(tmpflt % 10 >= 5)  
    {  
        tmpflt = tmpflt / 10 + 1;  
    }  
    else  
    {  
        tmpflt = tmpflt / 10;  
    }  
    printint(tmpint);  
    putchar('.');  
    printint(tmpflt);  

}  


void my_printf(const char *format,...)  
{  
    va_list ap;  
    va_start(ap,format);     //將ap指向第一個實際引數的地址
    while(*format)  
    {  
        if(*format != '%')  
        {  
            putchar(*format);  
            format++;  
        }  
        else  
        {  
            format++;  
            switch(*format)  
            {  
                case 'c':  
                {  
                    char valch = va_arg(ap,int);  //記錄當前實踐引數所在地址
                    printch(valch);  
                    format++;  
                    break;  
                }  
                case 'd':  
                {  
                    int valint = va_arg(ap,int);  
                    printint(valint);  
                    format++;  
                    break;  
                }  
                case 's':  
                {  
                    char *valstr = va_arg(ap,char *);  
                    printstr(valstr);  
                    format++;  
                    break;  
                }  
                case 'f':  
                {  
                    float valflt = va_arg(ap,double);  
                    printfloat(valflt);  
                    format++;  
                    break;  
                }  
                default:  
                {  
                    printch(*format);  
                    format++;  
                }  
            }    
        }  
    }
    va_end(ap);         
}  


int main()  
{  
    char ch = 'A';  
    char *str = "hello world";  
    int dec = 1234;  
    float flt = 1234.45678;   
    my_printf("ch = %c,str = %s,dec = %d,flt = %f\n",ch,str,dec,flt);  
    return 0;  
} 

 執行結果:

ch = A,str = hello world,dec = 1234,flt = 1234.4568





實際上,實現時可以用一個更簡單的函式,vprintf函式。

int vprintf(char *format, va_list param);

printf的功能就是用它來實現的,所不同的是,它用一個引數取代了變長引數表,且此引數是通過呼叫va_start巨集進行初始化。其實vprintf也是經過封裝的一個函式。
這樣就省了我們呼叫巨集對變參函式進行處理,只要開始呼叫一次va_start巨集進行一次初始化即可。


程式碼如下:

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


int my_printf(char *str,...)
{
    int n;    //記錄返回值
    va_list list;
    va_start(list,str);
    n=vprintf(str,list);
    va_end(list);
    return n;
}

int main()
{

    my_printf("%s,%d","hello world",10);

}

執行結果:

hello world,10