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/i int 輸出十進制有符號32bits整數,i是老式寫法 printf("%i",123);輸出123
o unsigned int 無符號8進制(octal)整數(不輸出前綴0) printf("0%o",123);輸出0173
u unsigned int 無符號10進制整數 printf("%u",123);輸出123
x/X unsigned int 無符號16進制整數,x對應的是abcdef,X對應的是ABCDEF(不輸出前綴0x) printf("0x%x 0x%X",123,123);輸出0x7b 0x7B
f/lf float(double) 單精度浮點數用f,雙精度浮點數用lf(printf可混用,但scanf不能混用) printf("%.9f %.9lf",0.000000123,0.000000123);輸出0.000000123 0.000000123。注意指定精度,否則printf默認精確到小數點后六位
e/E float(double) 科學計數法,使用指數(Exponent)表示浮點數,此處”e”的大小寫代表在輸出時“e”的大小寫 printf("%e %E",0.000000123,0.000000123);輸出1.230000e-07 1.230000E-07
g float(double) 根據數值的長度,選擇以最短的方式輸出,%f或%e printf("%g %g",0.000000123,0.123);輸出1.23e-07 0.123
G float(double) 根據數值的長度,選擇以最短的方式輸出,%f或%E printf("%G %G",0.000000123,0.123);輸出1.23E-07 0.123
c char 字符型。可以把輸入的數字按照ASCII碼相應轉換為對應的字符 printf("%c\n",64)輸出A
s char* 字符串。輸出字符串中的字符直至字符串中的空字符(字符串以空字符’\0‘結尾) printf("%s","測試test");輸出:測試test
S wchar_t* 寬字符串。輸出字符串中的字符直至字符串中的空字符(寬字符串以兩個空字符’\0‘結尾) setlocale(LC_ALL,"zh_CN.UTF-8");
wchar_t wtest[]=L"測試Test";
printf("%S\n",wtest);
輸出:測試test
p void* 以16進制形式輸出指針 printf("%010p","lvlv");輸出:0x004007e6
n int* 什么也不輸出。%n對應的參數是一個指向signed int的指針,在此之前輸出的字符數將存儲到指針所指的位置 int num=0;
printf("lvlv%n",&num);
printf("num:%d",num);
輸出:lvlvnum:4
% 字符% 輸出字符‘%’(百分號)本身 printf("%%");輸出:%
m 打印errno值對應的出錯內容 printf("%m\n");
a/A float(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.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時,一定使用小數點。默認的,如果沒有小數部分則不輸出小數點。
type是g、G時,尾部的0保留。
0 數字零 將輸出的前面補上0,直到占滿指定列寬為止(不可以搭配使用“-”)


示例:

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

輸出結果為:
這里寫圖片描述

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中的*,在輸出參數列表中指定精度。


示例:

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

輸出結果:

00001000
1000.12345679
1000.12345600
1000.1235
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引入的。

示例代碼:

printf("%hhd\n",'A'); //輸出有符號char printf("%hhu\n",'A'+128); //輸出無符號char printf("%hd\n",32767); //輸出有符號短整型short int printf("%hu\n",65535); //輸出無符號短整型unsigned short int printf("%ld\n",0x7fffffffffffffff); //輸出有符號長整型long int printf("%lu\n",0xffffffffffffffff); //輸出有符號長整型unsigned long int

輸出結果:

65
193
32767
65535
9223372036854775807
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 警報(響鈴)符
\b 回退符
\f 換頁符
\n 換行符
\r 回車符
\t 橫向制表符
\v 縱向制表符
\\ 反斜杠
\” 雙引號

4.關於printf緩沖

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

printf在glibc中默認為行緩沖,遇到一下幾種情況會刷新緩沖區,輸出內容:
(1)緩沖區填滿;
(2)寫入的字符中有換行符\n或回車符\r
(3)調用fflush手動刷新緩沖區;
(4)調用scanf要從輸入緩沖區中讀取數據時,也會將輸出緩沖區內的數據刷新。

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

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

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

5.小結

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

關於本文,個人存在兩個疑問。第一個是C++ Reference中還提到了一個type:%F,目前還沒有發現該type的用處與%f的區別所在,也請知之者留言告知,萬分感謝。第二個是在輸出寬字符串時,發現將printf和wprintf同時使用時,wprintf無法輸出,具體不知原因,這里建議不要同時使用printf和wprintf,以免發生錯誤。知道為何的讀者也請告知,謝謝!

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

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

上面的代碼中語句1和語句二不能同時存在,否則只能正常輸出第一個。原因尚不清楚,估計和wprintf和printf內部實現有關,也不知道在Windows平台是否也存在這種問題,有興趣的讀者可以嘗試一下。下一篇博文預估將集結全部火力,探討解決這個問題,wprintf的具體用法,字符編碼等相關問題,敬請期待。


參考文獻

[1]淺談C中的wprintf和寬字符顯示
[2]printf.C++ reference
[3]Why does printf not flush after the call unless a newline is in the format string?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM