做者介紹:守望,一名好文學,好技術的開發者。在我的公衆號【編程珠璣】上堅持分享原創技術文章,期待一塊兒交流學習。編程
閱讀字數:3575 | 9分鐘閱讀bash
printf多是咱們在學習C語言的過程當中最先接觸的庫函數了。其基本使用想必咱們都已經很是清楚了。可是下面的這些狀況你是否已經清楚地知道了呢?函數
咱們來看一個示例程序,看看你可否對下面的結果輸出有很是清晰的認識。學習
#include <stdio.h>
int main(void)
{
int a = 4;
int b = 3;
int c = a/b;
float d = *(float*)(&c);
long long e = 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
return 0;
}複製代碼
編譯爲32位程序:ui
gcc -m32 -o test test.c複製代碼
在運行以前,你能夠本身先猜測一下打印結果會是什麼。實際運行結果:spa
a/b:0.000000,a:3 //打印0的結果
(float)a/b:1.333333 //打印1的結果
(double)a/b:1.333333 //打印2的結果
d:0.000000 //打印3的結果
1.33333333333333325932 //打印4的結果
e:-1,a:-1 //打印5的結果
a:6,++a:6,a++:4 //打印6的結果複製代碼
你的猜測是否都正確呢?若是猜測錯誤,那麼接下來的內容你就不該該錯過了。3d
你是否會有如下疑問:code
0. 打印0的a/b爲何不是1,a爲何不是4?orm
1. 打印1和打印2有什麼區別呢?開發
2. 打印3爲何結果會是0.000000?
3. 打印4的結果爲何最後的小數位不對?其中的*是什麼意思?
4. 打印5中,爲何a的值是-1而不是4?
5. 打印6中,結果爲何分別是6,6,4?
在解答這些問題以前,咱們須要先了解一些基本內容。
printf是接受變長參數的函數,傳入printf中的參數個數能夠不定。而咱們在變長參數探究中說到:
調用者會對每一個參數執行「默認實際參數提高",提高規則以下:
——float將提高到double
——char、short和相應的signed、unsigned類型將提高到int
也就是說printf實際上只會接受到double,int,long int等類型的參數。而歷來不會實際接受到float,char,short等類型參數。
咱們能夠經過一個示例程序來檢驗:
//bad code
#include<stdio.h>
int main(void)
{
char *p = NULL;
printf("%d,%f,%c\n",p,p,p);
return 0;
}複製代碼
編譯報錯以下:
printf.c: In function ‘main’:
printf.c:5:12: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘char *’ [-Wformat=]
printf("%d,%f,%c\n",p,p,p);
^
printf.c:5:12: warning: format ‘%f’ expects argument of type ‘double’, but argument 3 has type ‘char *’ [-Wformat=]
printf.c:5:12: warning: format ‘%c’ expects argument of type ‘int’, but argument 4 has type ‘char *’ [-Wformat=]複製代碼
咱們能夠從報錯信息中看到:
%d 指望的是 int 類型參數
%f 指望的是 double 類型參數
%c 指望的也是 int 類型參數
而編譯之因此有警告是由於,char *類型沒法經過默認實際參數提高,將其提高爲int或double。
在C語言中,參數入棧順序是肯定的,從右往左。而參數的計算順序倒是沒有規定的。也就是說,編譯器能夠實現從右往左計算,也能夠實現從左往右計算。
對於double類型,其有效位爲15~~16位(參考:對浮點數的一些理解)。
printf中,*的使用可實現可變域寬和精度,使用時只須要用*替換域寬修飾符和精度修飾符便可。在這樣的狀況下,printf會從參數列表中取用實際值做爲域寬或者精度。示例程序以下:
#include<stdio.h>
int main(void)
{
float a = 1.33333333;
char *p = "hello";
printf("%.*f\n",6,a);
printf("%*s\n",8,p);
return 0;
}複製代碼
運行結果:
1.333333
hello複製代碼
而這裏的6或者8徹底能夠是一個宏定義或者變量,從而作到了動態地格式控制。
printf有不少格式控制符,例如%d,它在處理輸入時,會從堆棧中取其對應大小,即4個字節做爲對應的參數值。也就是說,當你傳入參數和格式控制符匹配或者在通過類型提高後和格式控制符匹配的時候,參數處理是沒有任何問題的。可是不匹配時,可能會出現未定義行爲(有兩種狀況例外,咱們後面再說)。例如,%f指望一個double(8字節)類型,可是傳入的參數是int(4字節),那麼在處理這個int參數值,可能會多處理4個字節,而且也會形成處理數據錯誤。
有了前面這些內容的鋪墊,咱們再來解答開始的疑問:
對於問題0,a/b的結果顯然爲4字節的int類型1,而%f指望的是8字節的double,而計算結果只有4個字節,所以會繼續格式化後面4個字節的a,而整型1和後面a組合成的8字節數據,按照浮點數的方式解釋時,它的值就是0.000000了。因爲前面已經讀取解釋了a的內容,所以第二個%d只能繼續讀取4個字節,也就是b的值3,最終就會出現打印a的值是3,而不是4。
對於問題1,實際上在printf中,是不須要%lf的,%f指望的就是double類型,在編譯最開始的示例程序其實就能夠發現這個事實。固然了在scanf函數中,這二者是有區別的。
對於問題2,也很簡單,2的二進制存儲形式按照浮點數方式解釋讀取時,就是該值。
對於問題3,double的有效位爲15~16位,也就是以外的位數都是不可靠的。printf中的*可用於實現可變域寬和精度,前面已經解釋過了。
對於問題4,這裏不給出,留給讀者思考,歡迎你們可留言區給出緣由。
對於問題5,雖然參數計算順序沒有規定,可是實際上至少對於gcc來講,它是從右往左計算的。也就是說,先計算a++,而a++是先用在加,即壓入a=4,其後,a的值變爲5;再計算++a,先加再用,即壓入a=5+1=6;最後a=6,壓入棧。最終從左往右壓入棧的值就分別爲6,6,4。也就是最終的打印結果。可是實際狀況中,這樣的代碼絕對不應出現!
至此,真相大白。
雖然咱們前面解釋了那些難以理解的現象,同時讀者能夠參考變長參數探究和對浮點數的一些理解找到更多的信息。可是咱們在實際編程中應該注意如下幾點:
格式控制符應該與對應參數類型匹配或者與類型提高後的參數類型匹配。
絕對避免出現計算結果與參數計算順序有關的代碼。
*在printf中實現可變域寬和精度。
printf不會實際接受到char,short和float類型參數。
若是%s對應的參數可能爲NULL或者對應整型,那將是一場災難。
不要忽略編譯器的任何警告,除非你很清楚你在作什麼。
例外狀況指的是有符號整型和無符號整型之間,以及void*和char*之間。
若是編譯爲64位程序運行,結果仍是同樣嗎?爲何?
以上爲今天的分享內容,謝謝你們!
編者:IT大咖說,轉載請標明版權和出處