pirntf()函數心得(筆試後的懺悔)

  筆試題

  printf()函數原型:intprintf(const char*format,[argument]);基本用法就不在贅述了,也不講一步步實現細節,估計很多人都會看不下去,這些網上都有。接下來主要講的是如何取數打印出來。

首先,來看一個例子(某知名IT公司的筆試題):

test1.c

#include<stdio.h>

intmain()

{

long long a=1;

long long b=2;

long long c=3;

printf("a=%d\nb=%d\nc=%d\n",a,b,c);

}

上面的程序結果是什麼?運行後可以看到,結果是:

a=1

b=0

c=2

看到上面的運行結果,很多人第一眼會很詫異,這裏我講講我的想法,不知道真實情況是不是我想的這樣,僅供參考,不對之處,還望指出。

首先先看內存的中的分佈情況。

test2.c

#include<iostream>

#include<stdio.h>

usingnamespace std;

intmain()

{

longlong a=1;

longlong b=2;

longlong c=3;

signedint *m;

m= (int *)&a;

for(int i=0; i < 6; i++)

{

if(i % 2 == 0)

printf("\n");

printf("%p=%d ",m+i,*(m+i));

}

printf("\n");

printf("&a=%p\n",&a);

printf("&b=%p\n",&b);

printf("&c=%p\n",&c);

printf("\n");

printf("a=%d,b=%d,c=%d\n",a,b,c);

printf("a=%lld,b=%d,c=%d\n",a,b,c);

printf("a=%d,b=%lld,c=%d\n",a,b,c);

printf("a=%d,b=%lld,c=%lld\n",a,b,c);

printf("\n\n\n");

longlong n = 8589934592;

int*Ln;

Ln= (int *)&n;

printf("%lld的高字節內容爲:%d\n",n,*(Ln+1));

printf("%lld的低字節內容爲:%d\n",n,*Ln);

return0;

}

運行結果:


由上面的程序運行結果可知,數據在內存中的分佈如下:

地址

存的內容

0xbffa88c0

1

0xbffa88c4

0

0xbffa88c8

2

0xbffa88cc

0

0xbffa88d0

3

0xbffa88d4

0

       printf("a=%d,b=%d,c=%d\n",a,b,c);取的是從地址a開始的前三個字節的內容。首先a=%d,從a開始取一個字節,因爲用了”%d”,所以輸出1,隨後b=%d,是緊跟這上次的地址(0xbffa88c4)後取一個字節,而不是從0xbffa88c8開始取的,我想可能printf只獲取第二個參數(本例子中的a)的地址,然後根據格式(%d)所代表的長度來取值,而不在乎其後bc的值是多少,只要有這個參數就行。

      爲了驗證以上想法,我們再來看

      printf("a=%lld,b=%d,c=%d\n",a,b,c);很容易知道符合以上想法的。

      printf("a=%d,b=%lld,c=%d\n",a,b,c);首先a取了第一個字節即1輸出,而後b取從0xbffa88c40xbffa88c8的內容,並且本機器是小尾端體系結構,即低地址存低字節,高低值存高字節,比如inta = 0x1234;低地址存的內容是0x34,高字節存的內容是0x12。所以輸出結果是8589934592

     printf("a=%d,b=%lld,c=%lld\n",a,b,c);的輸出結果同理分析。


printf()實現原理(類源代碼):

     後來查閱了一下書籍,是這麼介紹printf函數的。

     函數printf的正確聲明形式爲:

     int printf( char *fmt, ...);

     其中,省略號表示參數表中參數的數量和類型是可變的。省略號只能出現在參數表的尾部。printf的關鍵在於如何處理一個甚至連名字都沒有的參數表。標準頭文件<stdarg.h>中包含一組宏定義,它們堆如何遍歷參數表進行了定義。

     va_list類型用於聲明一個變量,該變量一次引用各參數。我們自己編寫一個printf的簡化版函數myprintf。在函數myprintf中,我們將定義一個va_list變量ap,意思是“參數指針”。宏va_start將ap初始化爲第一個無名參數的指針。在使用ap之前,該宏必須被調用一次。參數表必須至少包括一個有名參數,va_start將最後一個有名參數最爲起點。

     每次調用va_arg,該函數都將返回一個參數,並將ap指向下一個參數。va_arg使用一個類型名來決定返回的對象類型、指針移動的步長。最後,必須在函數返回之前調用va_end,以完成一些必要的清理工作。

     基於上面的討論,我們實現的簡化printf函數如下所示:

    

#include <stdarg.h>

/* myprintf函數:帶有可變參數列表的簡化的printf函數*/

void myprintf( char *fmt, ... )
{
    va_list ap; //依次指向每個無名參數
    char *p, *sval;
    int ival;
    double dval;

    va_start(ap,fmt);//將ap指向第一個無名參數
    for ( p = fmt; *p; p++ )
    {
	if ( *p != '%' )
	{
	    putchar(*p);
	    continue;
	}
	switch ( *++p )
	{
	    case 'd':
		ival = va_arg(ap,int);
		printf("%d",ival);
		break;
	    case 'f':
		dval = va_arg(ap,double);
		printf("%f",dval);
		break;
	    case 's':
		for ( sval = va_arg(ap,char *); *sval; sval++ )
		    putchar(*sval);
		break;
	    default:
		putchar(*p);
		break;
	}
    }
    va_end(ap);//結束時的清理工作
}


     如有錯誤,還望指出,謝謝!

發佈了41 篇原創文章 · 獲贊 30 · 訪問量 31萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章