當前位置:
首頁 > 知識 > printf()函數詳解

printf()函數詳解

1.printf()簡介

printf()是C語言標準庫函數,用於將格式化後的字元串輸出到標準輸出。標準輸出,即標準輸出文件,對應終端的屏幕。printf()申明於頭文件stdio.h。

函數原型:

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

  • 1

返回值:

正確返回輸出的字元總數,錯誤返回負值,與此同時,輸入輸出流錯誤標誌將被置值,可由指示器ferror來檢查輸入輸出流的錯誤標誌。

調用格式:

printf()函數的調用格式為:printf("格式化字元串",輸出表列)。

格式化字元串包含三種對象,分別為:

(1)字元串常量;

(2)格式控制字元串;

(3)轉義字元。

字元串常量原樣輸出,在顯示中起提示作用。輸出表列中給出了各個輸出項,要求格式控制字元串和各輸出項在數量和類型上應該一一對應。其中格式控制字元串是以%開頭的字元串,在%後面跟有各種格式控制符,以說明輸出數據的類型、寬度、精度等。

註:本文的所有示例代碼均在Linux環境下以g++ 4.4.6編譯成64位程序的執行。


2.格式控制字元串詳解

printf的格式控制字元串組成如下:

%[flags][width][.prec][length]type

  • 1

分別為:

%[標誌][最小寬度][.精度][類型長度]類型。

  • 1

2.1類型(type)

首先說明類型,因為類型是格式控制字元串的重中之重,是必不可少的組成部分,其它的選項都是可選的。type用於規定輸出數據的類型,含義如下:

字元對應數據類型含義示例d/iint輸出十進位有符號32bits整數,i是老式寫法printf("%i",123);輸出123ounsigned int無符號8進位(octal)整數(不輸出前綴0)printf("0%o",123);輸出0173uunsigned int無符號10進位整數printf("%u",123);輸出123x/Xunsigned int無符號16進位整數,x對應的是abcdef,X對應的是ABCDEF(不輸出前綴0x)printf("0x%x 0x%X",123,123);輸出0x7b 0x7Bf/lffloat(double)單精度浮點數用f,雙精度浮點數用lf(printf可混用,但scanf不能混用)printf("%.9f %.9lf",0.000000123,0.000000123);輸出0.000000123 0.000000123。注意指定精度,否則printf默認精確到小數點後六位Ffloat(double)與f格式相同,只不過 infinity 和 nan 輸出為大寫形式。例如printf("%f %F %f %F
",INFINITY,INFINITY,NAN,NAN);輸出結果為inf INF nan NANe/Efloat(double)科學計數法,使用指數(Exponent)表示浮點數,此處」e」的大小寫代表在輸出時「e」的大小寫printf("%e %E",0.000000123,0.000000123);輸出1.230000e-07 1.230000E-07gfloat(double)根據數值的長度,選擇以最短的方式輸出,%f或%eprintf("%g %g",0.000000123,0.123);輸出1.23e-07 0.123Gfloat(double)根據數值的長度,選擇以最短的方式輸出,%f或%Eprintf("%G %G",0.000000123,0.123);輸出1.23E-07 0.123cchar字元型。可以把輸入的數字按照ASCII碼相應轉換為對應的字元printf("%c
",64)輸出Aschar*字元串。輸出字元串中的字元直至字元串中的空字元(字元串以空字元』『結尾)printf("%s","測試test");輸出:測試testSwchar_t*寬字元串。輸出字元串中的字元直至字元串中的空字元(寬字元串以兩個空字元』『結尾)setlocale(LC_ALL,"zh_CN.UTF-8");

wchar_t wtest[]=L"測試Test";

printf("%S
",wtest);

輸出:測試testpvoid*以16進位形式輸出指針printf("%010p","lvlv");輸出:0x004007e6nint*什麼也不輸出。%n對應的參數是一個指向signed int的指針,在此之前輸出的字元數將存儲到指針所指的位置int num=0;

printf("lvlv%n",&num);

printf("num:%d",num);

輸出:lvlvnum:4%字元%輸出字元『%』(百分號)本身printf("%%");輸出:%m無列印errno值對應的出錯內容printf("%m
");a/Afloat(double)十六進位p計數法輸出浮點數,a為小寫,A為大寫printf("%a %A",15.15,15.15);輸出:0x1.e4ccccccccccdp+3 0X1.E4CCCCCCCCCCDP+3

注意:

(1)使用printf輸出寬字元時,需要使用setlocale指定本地化信息並同時指明當前代碼的編碼方式。除了使用%S,還可以使用%ls。

(2)%a和%A是C99引入的格式化類型,採用十六進位p計數法輸出浮點數。p計數法類似E科學計數法,但不同。數以0x開頭,然後是16進位浮點數部分,接著是p後面是以 2為底的階碼。以上面輸出的15.15為例,推算輸出結果。15.15轉換成二進位為1111.00 1001 1001 1001 1001 ...,因為二進位表示數值的離散特點,計算機對於小數有時是不能精確表示的,比如0.5可以精確表示為0.120.12,而0.15卻不能精確表示。將15.15對應的二進位右移三位,為1.1110 0100 1100 1100 1100 ...轉換對應的十六進位就是0x1.e4ccccccccccd,注意舍入時向高位進了1位。由於右移三位,所以二進位階碼就是3。最後的結果就是0x1.e4ccccccccccdp+3。

(3)格式控制字元串除了指明輸出的數據類型,還可以包含一些其它的可選的格式說明,依序有 flags, width, .precision and length。下面一一講解。

2.2標誌(flags)

flags規定輸出樣式,取值和含義如下:

字元名稱說明-減號結果左對齊,右邊填空格。默認是右對齊,左邊填空格。+加號輸出符號(正號或負號)space空格輸出值為正時加上空格,為負時加上負號#井號type是o、x、X時,增加前綴0、0x、0X。

type是a、A、e、E、f、g、G時,一定使用小數點。默認的,如果使用.0控制不輸出小數部分,則不輸出小數點。

type是g、G時,尾部的0保留。0數字零將輸出的前面補上0,直到佔滿指定列寬為止(不可以搭配使用「-」)

示例:

  1. printf("%5d
    ",1000); //默認右對齊,左邊補空格
  2. printf("%-5d
    ",1000); //左對齊,右邊補空格
  3. printf("%+d %+d
    ",1000,-1000); //輸出正負號
  4. printf("% d % d
    ",1000,-1000); //正號用空格替代,負號輸出
  5. printf("%x %#x
    ",1000,1000); //輸出0x
  6. printf("%.0f %#.0f
    ",1000.0,1000.0)//當小數點後不輸出值時依然輸出小數點
  7. printf("%g %#g
    ",1000.0,1000.0); //保留小數點後後的0
  8. printf("%05d
    ",1000); //前面補0

輸出結果為:

2.3輸出最小寬度(width)

用十進位整數來表示輸出的最少位數。若實際位數多於指定的寬度,則按實際位數輸出,若實際位數少於定義的寬度則補以空格或0。width的可能取值如下:

width描述示例數值十進位整數printf("%06d",1000);輸出:001000*星號。不顯示指明輸出最小寬度,而是以星號代替,在printf的輸出參數列表中給出printf("%0*d",6,1000);輸出:001000

2.4精度(.precision)

精度格式符以「.」開頭,後跟十進位整數。可取值如下:

.precision描述.數值十進位整數。

(1)對於整型(d,i,o,u,x,X),precision表示輸出的最小的數字個數,不足補前導零,超過不截斷。

(2)對於浮點型(a, A, e, E, f ),precision表示小數點後數值位數,默認為六位,不足補後置0,超過則截斷。

(3)對於類型說明符g或G,表示可輸出的最大有效數字。

(4)對於字元串(s),precision表示最大可輸出字元數,不足正常輸出,超過則截斷。

precision不顯示指定,則默認為0.*以星號代替數值,類似於width中的*,在輸出參數列表中指定精度。

示例:

  1. printf("%.8d
    ",1000); //不足指定寬度補前導0,效果等同於%06d
  2. printf("%.8f
    ",1000.123456789);//超過精度,截斷
  3. printf("%.8f
    ",1000.123456); //不足精度,補後置0
  4. printf("%.8g
    ",1000.123456); //最大有效數字為8位
  5. printf("%.8s
    ",「abcdefghij」); //超過指定長度截斷
  • 1
  • 2
  • 3
  • 4
  • 5

輸出結果:

  1. 00001000
  2. 1000.12345679
  3. 1000.12345600
  4. 1000.1235
  5. abcdefgh
  • 1
  • 2
  • 3
  • 4
  • 5

注意,在對浮點數和整數截斷時,存在四捨五入。

2.5類型長度(length)

類型長度指明待輸出數據的長度。因為相同類型可以有不同的長度,比如整型有16bits的short int,32bits的int,也有64bits的long int,浮點型有32bits的單精度float和64bits的雙精度double。為了指明同一類型的不同長度,於是乎,類型長度(length)應運而生,成為格式控制字元串的一部分。

因為Markdown表格不支持單元格合併,背景顏色等樣式,所以直接引用printf.C++ reference的表格。

注意:黃色背景行標識的類型長度說明符和相應的數據類型是C99引入的。

示例代碼:

  1. printf("%hhd
    ","A"); //輸出有符號char
  2. printf("%hhu
    ","A"+128); //輸出無符號char
  3. printf("%hd
    ",32767); //輸出有符號短整型short int
  4. printf("%hu
    ",65535); //輸出無符號短整型unsigned short int
  5. printf("%ld
    ",0x7fffffffffffffff); //輸出有符號長整型long int
  6. printf("%lu
    ",0xffffffffffffffff); //輸出有符號長整型unsigned long int
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

輸出結果:

  1. 65
  2. 193
  3. 32767
  4. 65535
  5. 9223372036854775807
  6. 18446744073709551615
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

注意:

long int到底是32bits還是64bits跟生成的程序是32bits還是64bits一一對應,如果使用g++編譯程序的話,可通過-m32或-m64選項分別生成32bits和64bits的程序。因本人測試代碼編譯生成的是64bits的程序,所以long int也就是64btis。


3.轉義字元

轉義字元在字元串中會被自動轉換為相應操作命令。printf()使用的常見轉義字元如下:

轉義字元意義a警報(響鈴)符回退符f換頁符
換行符
回車符 橫向製表符v縱向製表符\反斜杠」雙引號


4.關於printf緩衝

在printf的實現中,在調用write之前先寫入IO緩衝區,這是一個用戶空間的緩衝。系統調用是軟中斷,頻繁調用,需要頻繁陷入內核態,這樣的效率不是很高,而printf實際是向用戶空間的IO緩衝寫,在滿足條件的情況下才會調用write系統調用,減少IO次數,提高效率。

printf在glibc中默認為行緩衝,遇到以下幾種情況會刷新緩衝區,輸出內容:

(1)緩衝區填滿;

(2)寫入的字元中有換行符
或回車符

(3)調用fflush手動刷新緩衝區;

(4)調用scanf要從輸入緩衝區中讀取數據時,也會將輸出緩衝區內的數據刷新。

可使用setbuf(stdout,NULL)關閉行緩衝,或者setbuf(stdout,uBuff)設置新的緩衝區,uBuff為自己指定的緩衝區。也可以使用setvbuf(stdout,NULL,_IOFBF,0);來改變標準輸出為全緩衝。全緩衝與行緩衝的區別在於遇到換行符不刷新緩衝區。

printf在VC++中默認關閉緩衝區,輸出時會及時的輸到屏幕[3][3]。如果顯示開啟緩衝區,只能設置全緩衝。因為微軟閉源,所以無法研究printf函數的實現源碼。

Linux和Windows下的緩衝區管理可見:C的全緩衝、行緩衝和無緩衝。


5.printf與wprintf不能同時使用

該小結寫在2018年1月15日。兩年後的今日,在網上苦苦搜索尋求答案,終於解決了之前的疑惑。

在輸出寬字元串時,發現將printf和wprintf同時使用時,則後使用的函數沒有輸出。這裡建議不要同時使用printf和wprintf,以免發生錯誤。

printf和wprintf不能同時輸出寬字元串的示例代碼如下:

  1. #include <stdio.h>
  2. #include <wchar.h>
  3. #include <locale.h>
  4. int main(int argc,char* argv[]){
  5. char test[]="測試Test";
  6. setlocale(LC_ALL,"zh_CN.UTF-8");
  7. wchar_t wtest[]=L"0m~K0m~UTest";
  8. printf("printf:%S
    ",wtest); //語句1:可正常輸出"測試Test"
  9. wprintf(L"wprintf:%S
    ",wtest); //語句2:無任何內容輸出
  10. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

上面的代碼中語句1和語句二不能同時存在,否則只能正常輸出第一個。也不知道在Windows平台是否也存在這種問題,有興趣的讀者可以嘗試一下。關於原因,GNU官方文檔中有明確說明不能同時使用printf與wprintf,參見The GNU C Library Section 12.6 Streams in Internationalized Applications,內容如下:

It is important to never mix the use of wide and not wide operations on a stream. There are no diagnostics issued. The application behavior will simply be strange or the application will simply crash.

  • 1

這裡是因為輸出流在被創建時,不存在流定向,一旦使用了printf(多位元組流)或wprintf(寬字元流)後,就被設置為對應的流定向,且無法更改。可以使用如下函數獲取當前輸出流的流定向。

  1. //
  2. //@param:stream:文件流;mode:取值可以>0,=0或<0
  3. //@ret:<0:流已被設置為多位元組流定向;=0:流尚未被設置;>0:流已被設置為寬字元流定向
  4. //
  5. int fwide (FILE* stream, int mode);
  6. //獲取當前標準輸出流定向
  7. int ret=fwide(stdout,0);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

通過fwide可以設置當前流定向,前提是未有任何的I/O操作,也就是當前流尚未被設置任何流定向。順帶吐槽一下,不知為何標準庫函數fwide為何實現的如此受限。具體操作如下:

  1. //設置標準輸出流定向為多位元組流定向
  2. fwide(stdout,-1);
  3. //設置標準輸出流定向為寬字元流定向
  4. fwide(stdout,1);
  • 1
  • 2
  • 3
  • 4
  • 5

既然GNU C存在這個問題,那該如何解決呢?這裡有兩種辦法:

(1)統一使用一種函數。

例如:

  1. wprintf(L"%s","a
    ");
  2. wprintf(L"b
    ");
  • 1
  • 2

  1. printf("a
    ");
  2. printf("%ls
    ",L"b");
  • 1
  • 2

(2)使用freopen清空流定向。

  1. //重新打開標準輸出流,清空流定向
  2. FILE* pFile=freopen("/dev/tty","w",stdout);
  3. wprintf(L"wide freopen succeeded
    ");
  4. //重新打開標準輸出流,清空流定向
  5. pFile=freopen("/dev/tty","w",stdout);
  6. printf("narrow freopen succeeded
    ");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

上面可以讓printf與wprintf同時使用。


6.小結

耗時將近兩年,終於完成了此篇看似基礎,但卻紛繁複雜的printf()用法。由於時間和個人水平有限,文章不足之處在所難免,也請讀者批評指正,不甚感激。

printf()函數詳解

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 程序員小新人學習 的精彩文章:

Linux 常用基本命令 rmdir rm
spring-boot 之 使員Druid 整合Mybatis 最簡配置多數據源

TAG:程序員小新人學習 |