前言
printf可能是我們?cè)趯W(xué)習(xí)C語(yǔ)言的過(guò)程中最早接觸的庫(kù)函數(shù)了。其基本使用想必我們都已經(jīng)非常清楚了。但是下面的這些情況你是否已經(jīng)清楚地知道了呢?
示例程序
我們來(lái)看一個(gè)示例程序,看看你能否對(duì)下面的結(jié)果輸出有非常清晰的認(rèn)識(shí)。
#include intmain(void) { inta=4; intb=3; intc=a/b; floatd=*(float*)(&c); longlonge=0xffffffffffffffff; printf("a/b:%f,a:%d\n",a/b,a,b);//打印0 printf("(float)a/b:%f\n",((float)a)/b);//打印1 printf("(double)a/b:%lf\n",((double)a)/b);//打印2 printf("d:%f\n",d);//打印3 printf("%.*f\n",20,(double)a/b);//打印4 printf("e:%d,a:%d\n",e,a);//打印5 printf("a:%d,++a:%d,a++:%d\n",a,++a,a++);//打印6 return0; }
編譯為32位程序:
gcc-m32-otesttest.c
在運(yùn)行之前,你可以自己先猜想一下打印結(jié)果會(huì)是什么。實(shí)際運(yùn)行結(jié)果:
a/b:0.000000,a:3//打印0的結(jié)果 (float)a/b:1.333333//打印1的結(jié)果 (double)a/b:1.333333//打印2的結(jié)果 d:0.000000//打印3的結(jié)果 1.33333333333333325932//打印4的結(jié)果 e:-1,a:-1//打印5的結(jié)果 a:6,++a:6,a++:4//打印6的結(jié)果
你的猜想是否都正確呢?如果猜想錯(cuò)誤,那么接下來(lái)的內(nèi)容你就不應(yīng)該錯(cuò)過(guò)了。
你是否會(huì)有以下疑問(wèn):
0.打印0的a/b為什么不是1,a為什么不是4?
1.打印1和打印2有什么區(qū)別呢?
2.打印3為什么結(jié)果會(huì)是0.000000?
3.打印4的結(jié)果為什么最后的小數(shù)位不對(duì)?其中的*是什么意思?
4.打印5中,為什么a的值是-1而不是4?
5.打印6中,結(jié)果為什么分別是6,6,4?
在解答這些問(wèn)題之前,我們需要先了解一些基本內(nèi)容。
可變參數(shù)中的類(lèi)型提升
printf是接受變長(zhǎng)參數(shù)的函數(shù),傳入printf中的參數(shù)個(gè)數(shù)可以不定。而我們?cè)谧冮L(zhǎng)參數(shù)探究中說(shuō)到:
調(diào)用者會(huì)對(duì)每個(gè)參數(shù)執(zhí)行“默認(rèn)實(shí)際參數(shù)提升",提升規(guī)則如下:
——float將提升到double
——char、short和相應(yīng)的signed、unsigned類(lèi)型將提升到int
也就是說(shuō)printf實(shí)際上只會(huì)接受到double,int,long int等類(lèi)型的參數(shù)。而從來(lái)不會(huì)實(shí)際接受到float,char,short等類(lèi)型參數(shù)。
我們可以通過(guò)一個(gè)示例程序來(lái)檢驗(yàn):
//badcode #include intmain(void) { char*p=NULL; printf("%d,%f,%c\n",p,p,p); return0; }
編譯報(bào)錯(cuò)如下:
printf.c:Infunction‘main’: printf.c:5:12:warning:format‘%d’expectsargumentoftype‘int’,butargument2hastype‘char*’[-Wformat=] printf("%d,%f,%c\n",p,p,p); ^ printf.c:5:12:warning:format‘%f’expectsargumentoftype‘double’,butargument3hastype‘char*’[-Wformat=] printf.c:5:12:warning:format‘%c’expectsargumentoftype‘int’,butargument4hastype‘char*’[-Wformat=]
我們可以從報(bào)錯(cuò)信息中看到:
%d 期望的是 int 類(lèi)型參數(shù)
%f 期望的是 double 類(lèi)型參數(shù)
%c 期望的也是 int 類(lèi)型參數(shù)
而編譯之所以有警告是因?yàn)椋琧har *類(lèi)型無(wú)法通過(guò)默認(rèn)實(shí)際參數(shù)提升,將其提升為int或double。
參數(shù)入棧順序以及計(jì)算順序
在C語(yǔ)言中,參數(shù)入棧順序是確定的,從右往左。而參數(shù)的計(jì)算順序卻是沒(méi)有規(guī)定的。也就是說(shuō),編譯器可以實(shí)現(xiàn)從右往左計(jì)算,也可以實(shí)現(xiàn)從左往右計(jì)算。
浮點(diǎn)數(shù)的有效位
對(duì)于double類(lèi)型,其有效位為15~~16位(參考:對(duì)浮點(diǎn)數(shù)的一些理解)。
可變域?qū)捄途?/p>
printf中,*的使用可實(shí)現(xiàn)可變域?qū)捄途龋褂脮r(shí)只需要用*替換域?qū)捫揎椃途刃揎椃纯伞T谶@樣的情況下,printf會(huì)從參數(shù)列表中取用實(shí)際值作為域?qū)捇蛘呔取J纠绦蛉缦拢?/p>
#include intmain(void) { floata=1.33333333; char*p="hello"; printf("%.*f\n",6,a); printf("%*s\n",8,p); return0; }
運(yùn)行結(jié)果:
1.333333 hello
而這里的6或者8完全可以是一個(gè)宏定義或者變量,從而做到了動(dòng)態(tài)地格式控制。
格式控制符是如何處理參數(shù)的
printf有很多格式控制符,例如%d,它在處理輸入時(shí),會(huì)從堆棧中取其對(duì)應(yīng)大小,即4個(gè)字節(jié)作為對(duì)應(yīng)的參數(shù)值。也就是說(shuō),當(dāng)你傳入?yún)?shù)和格式控制符匹配或者在經(jīng)過(guò)類(lèi)型提升后和格式控制符匹配的時(shí)候,參數(shù)處理是沒(méi)有任何問(wèn)題的。但是不匹配時(shí),可能會(huì)出現(xiàn)未定義行為(有兩種情況例外,我們后面再說(shuō))。例如,%f期望一個(gè)double(8字節(jié))類(lèi)型,但是傳入的參數(shù)是int(4字節(jié)),那么在處理這個(gè)int參數(shù)值,可能會(huì)多處理4個(gè)字節(jié),并且也會(huì)造成處理數(shù)據(jù)錯(cuò)誤。
真相大白
有了前面這些內(nèi)容的鋪墊,我們?cè)賮?lái)解答開(kāi)始的疑問(wèn):
對(duì)于問(wèn)題0,a/b的結(jié)果顯然為4字節(jié)的int類(lèi)型1,而%f期望的是8字節(jié)的double,而計(jì)算結(jié)果只有4個(gè)字節(jié),因此會(huì)繼續(xù)格式化后面4個(gè)字節(jié)的a,而整型1和后面a組合成的8字節(jié)數(shù)據(jù),按照浮點(diǎn)數(shù)的方式解釋時(shí),它的值就是0.000000了。由于前面已經(jīng)讀取解釋了a的內(nèi)容,因此第二個(gè)%d只能繼續(xù)讀取4個(gè)字節(jié),也就是b的值3,最終就會(huì)出現(xiàn)打印a的值是3,而不是4。
對(duì)于問(wèn)題1,實(shí)際上在printf中,是不需要%lf的,%f期望的就是double類(lèi)型,在編譯最開(kāi)始的示例程序其實(shí)就可以發(fā)現(xiàn)這個(gè)事實(shí)。當(dāng)然了在scanf函數(shù)中,這兩者是有區(qū)別的。
對(duì)于問(wèn)題2,也很簡(jiǎn)單,2的二進(jìn)制存儲(chǔ)形式按照浮點(diǎn)數(shù)方式解釋讀取時(shí),就是該值。
對(duì)于問(wèn)題3,double的有效位為15~16位,也就是之外的位數(shù)都是不可靠的。printf中的*可用于實(shí)現(xiàn)可變域?qū)捄途龋懊嬉呀?jīng)解釋過(guò)了。
對(duì)于問(wèn)題4,這里不給出,留給讀者思考,歡迎大家可留言區(qū)給出原因。
對(duì)于問(wèn)題5,雖然參數(shù)計(jì)算順序沒(méi)有規(guī)定,但是實(shí)際上至少對(duì)于gcc來(lái)說(shuō),它是從右往左計(jì)算的。也就是說(shuō),先計(jì)算a++,而a++是先用在加,即壓入a=4,其后,a的值變?yōu)?;再計(jì)算++a,先加再用,即壓入a=5+1=6;最后a=6,壓入棧。最終從左往右壓入棧的值就分別為6,6,4。也就是最終的打印結(jié)果。但是實(shí)際情況中,這樣的代碼絕對(duì)不該出現(xiàn)!
至此,真相大白。
總結(jié)
雖然我們前面解釋了那些難以理解的現(xiàn)象,同時(shí)讀者可以參考變長(zhǎng)參數(shù)探究和對(duì)浮點(diǎn)數(shù)的一些理解找到更多的信息。但是我們?cè)趯?shí)際編程中應(yīng)該注意以下幾點(diǎn):
格式控制符應(yīng)該與對(duì)應(yīng)參數(shù)類(lèi)型匹配或者與類(lèi)型提升后的參數(shù)類(lèi)型匹配。
絕對(duì)避免出現(xiàn)計(jì)算結(jié)果與參數(shù)計(jì)算順序有關(guān)的代碼。
*在printf中實(shí)現(xiàn)可變域?qū)捄途取?/p>
printf不會(huì)實(shí)際接受到char,short和float類(lèi)型參數(shù)。
如果%s對(duì)應(yīng)的參數(shù)可能為NULL或者對(duì)應(yīng)整型,那將是一場(chǎng)災(zāi)難。
不要忽略編譯器的任何警告,除非你很清楚你在做什么。
例外情況指的是有符號(hào)整型和無(wú)符號(hào)整型之間,以及void*和char*之間。
-
打印
+關(guān)注
關(guān)注
1文章
63瀏覽量
18653 -
程序
+關(guān)注
關(guān)注
115文章
3720瀏覽量
80359 -
Printf
+關(guān)注
關(guān)注
0文章
81瀏覽量
13564
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論