【C語言入門到精通 03】格式化輸入/輸出

03 格式化輸入/輸出

A programming language is low level when its programs require attention to the irrelevant.1

碼字不易,對你有幫助 **點贊👍/轉發↪️/關注 👀 ** 支持一下作者
微信搜公衆號:不會編程的程序圓
看更多幹貨,獲取第一時間更新

請將本片與下一節《數據類型》 聯繫起來一起“食用”。

注:本教程含有超綱內容!!!如果你看不懂,不要喪失信心,可以“不求甚解”一些,關鍵是要多寫代碼!然後繼續學習下面的內容!

🗺思維導圖


✉️寫在前面


如果只是寫個人學習總結的博客很容易,簡單寫一些感悟然後貼上代碼走人就可以了,甚至不用校審。但是我命名本系列爲【C語言必知必會】幫助你從入門到精通 C語言,那勢必要“事無鉅細”一些:既要考慮到沒有基礎的初學者,又不能止於基礎。所以本教程適合各類人羣學習,只要你看就一定會有幫助。

本教程是本人純手打併排版,校審由我與我的搭檔湯圓君一起完成的。你們看這一篇文章我要寫好幾個小時。如果文章對你有幫助,請不要“白嫖”。支持一下作者,作者會發更多幹貨好文。

特別鳴謝:湯圓君(公衆號:【Cc聽香水榭】 長期更新高質量英語教學)關注她表示對她工作的認可吧!

▶️ 此符號表示該內容以後的章節會講解,此章節內不要求理解。

🌐目錄


printf 函數

printf()函數打印數據的指令要與待打印數據的類型相匹配。例如,打印整數時使用 %d,打印字符時使用 %c 。這些符號被稱爲轉換說明(conversion specification),它們指定了如何把數據(以2進制形式)轉換成可顯示的形式。

例如:

printf("I am %d years old", 18);

這是 printf()的格式:

printf(格式字符串,待打印項1,待打印項2,...);

待打印項都是要打印的的項。它們可以是變量,常量,甚至是在打印之前計算的表達式。上例中,只有一個待打印項: 18 。

格式字符串包含兩種不同信息:

  • 普通字符:以字符串中出現的形式打印出來。上例中,“I am” 與 " years old" 爲普通字符
  • 轉換說明:用待打印項的值來替換。上例中,"%d" 爲轉換說明

⚠️

C語言的編譯器不會檢測格式字符串中轉換說明中的數量與待打印項總個數是否相匹配。

1.缺少參數

printf("%d %d\n", i); // wrong

printf 會正確顯示 i 的值,然後顯示一個無意義的整數值。

2.參數過多

printf("%d\n", i, j);// wrong

而在這種情況下,printf 函數會顯示變量 i 的值,但是不會顯示變量 j 的值


printf 轉換說明

轉換說明這部分我做了很久,比較詳細,配合下一章數據類型才能看懂大部分,剩下的就需要你在不斷使用的過程中領悟了。

  • 標誌(可選,允許出現多於一個)

    - 字段內左對齊(默認右對齊)
    + 在打印的數前加上 + 或 - (通常只有負數前面附上減號)例1
    空格 在打印的非負數前前面加空格( + 標誌優先於空格標誌)例2
    # 對象:八進制數,十六進制數,以g/G 轉換輸出的數 例3
    0 用前導 0 在字段寬度內對輸出進行填充。如果轉換格式爲d,i,o,u,x(X),而且指定了精度,可以忽略 0 例4

    例 1:

    	printf("%d\n", 123);
    	printf("%d\n", -123);
    	printf("%+d\n", 123);
    	printf("%+d\n", -123);
    
    123
    -123
    +123
    -123
    

    例 2:

    printf("% d\n", 123);
    printf("% d\n", -123);
    printf("% +d\n", 123);
    
     123
    -123
    +123
    

    例 3:

    printf("%o\n", 0123);
    printf("%x\n", 0x123);
    printf("%#o\n", 0123);
    printf("%#x\n", 0x123);
    printf("%#g\n", 123.0);
    printf("%g\n", 123.0);
    
    123
    123
    0123
    0x123
    123.000
    123
    

    例 4:

    printf("%5d\n", 123);
    printf("%05d\n", 123);
    printf("%5.3d\n", 123);
    
      123
    00123
      123
    
  • 最小字段寬度(可選)

    如果數據項太小無法達到這個寬度,那麼會對字段進行填充。(默認情況下會在數據項左側添加空格,從而使字段寬度內右對齊)。

    如果數據項過大以至於超過了這個寬度,那麼會完整的顯示數據項。

    字段寬度可以是整數也可以是字符 *。如果是字符 * ,那麼字段寬度由下一個參數決定。如果這個參數爲負,它會被視爲前面帶 - 標誌的正數。例5

    例 5:

    printf("%5d\n", 123);
    printf("%2d\n", 123);
    printf("%*d\n", 5, 123);
    printf("%*d\n", -5, 123);
    
     123
    123
     123
    123
    
  • 精度(可選項)

    如果轉換說明是:

    d,i,o,u,x,X, 那麼精度表示最少位數(如果位數不夠,則添加前導 0 )

    a,A,e,E,f,F ,那麼精度表示小數點後的位數

    g,G,那麼精度表示有效數字個數

    s,那麼精度表示最大字節數

    精度是由小數點(.)後跟一個整數或 * 字符構成的。如果是 * ,那麼精度由下一個參數決定(如果這個參數爲負,效果與不指定精度一樣。)如果只有小數點,那麼精度爲0 。例 6

    例 6:

    printf("%.4d\n", 123);
    
    printf("\n");
    
    printf("%f\n", 123.0);
    printf("%.1f\n", 123.0);
    
    printf("\n");
    
    printf("%g\n", 123.0);
    printf("%.5g\n", 123.0);
    
    printf("\n");
    
    printf("%s\n", "Hello");
    printf("%.2s\n", "Hello");
    
    printf("\n");
    
    printf("%.*d\n", 4, 123);
    printf("%.*d\n", -4, 123);
    
    0123
    
    123.000000
    123.0
    
    123
    123
    
    Hello
    He
    
    0123
    123
    
  • 長度修飾符(可選)。

    長度修飾符表明待顯示的數據項的長度大於或小於特定轉換說明中的正常值。例7

    長度修飾符 轉換說明符 含義
    hh (C99) d,i,o,u,x,X signed char, unsigned char
    h d,i,o,u,x,X short, unsigned short
    l d,i,o,u,x,X long, unsigned long
    ll (C99) d,i,o,u,x,X long long, unsigned long long
    L a,A,e,E,f,F,g,G long double
    z (C99) d,i,o,u,x,X size_t
    j (C99) d,i,o,u,x,X ptrdiff_t

    例 7:

    printf("%#hhX\n", 0xAABBCCDDEEFF1122);//這是一個佔用內存爲 8 個字節的十六進制數
    printf("%#hX\n", 0xAABBCCDDEEFF1122);
    printf("%#X\n", 0xAABBCCDDEEFF1122);
    printf("%#lX\n", 0xAABBCCDDEEFF1122);
    printf("%#llX\n", 0xAABBCCDDEEFF1122);
    
    0X22
    0X1122
    0XEEFF1122
    0XEEFF1122
    0XAABBCCDDEEFF1122
    
  • 轉換說明符

    由於參數提升(▶️),在實參傳遞給可變數量實參函數時,float 會轉換爲 double ,char 會轉換爲 int。例8

    轉換說明符 含義
    d,i 把 int 類型轉換爲 十進制形式
    o,u,x,X 把無符號整型轉換爲八進制(o),十進制(u),十六進制形式(x,X)。
    f,F (F C99) 把 double 類型轉換爲 十進制形式,並把小數點放置在正確位置上。如果沒有指定精度,那麼小數點後顯示6個數字。
    e,E 把 double 類型轉換爲 科學計數法形式。如果沒有指定精度,那麼小數點後顯示6個數字。
    g,G 把double 類型轉換爲 f 形式或 e 形式。當數值的指數部分小於 -4,或大於等於精度時,會選擇以 e 的形式顯示。尾部的 0 不顯示(除非用#標誌),且小數點後跟有數字纔會顯示出來。
    a,A (C99) 把 double 類型轉換爲十六進制科學計數法(p計數法)。
    c 顯示無符號字符的 int 類型值。
    s 寫出由實參指向的字符串。
    p 把 void* 類型轉換爲可打印的形式。
    n 相應的實參必須是指向 int 型對象的指針。在該對象中存儲 …printf 函數已經輸出的字符數量,不產生輸出。
    % 寫字符 %

    例 8:

    printf("%i\n", 123);
    printf("%d\n", 123);
    
    printf("%o\n", 123);
    printf("%u\n", 123);
    printf("%x\n", 123);
    printf("%X\n", 123);
    
    printf("%f\n", 123.0);
    
    printf("%e\n", 123.0);
    
    printf("%g\n", 123.0);
    
    printf("%a\n", 123);
    
    printf("%c\n", 65);
    
    printf("%s\n", "123");
    
    int* a = 2;
    printf("%p\n", a);
    
    printf("%%\n");
    

    輸出:爲了方便大家觀看我已經將輸出中的換行刪除了

    123
    123
    
    173
    123
    7b
    7B
    
    123.000000
    
    1.230000e+02
    
    123
    
    0x1.e13430000007bp-1021
    
    A
    
    123
    
    00000002
    
    %
    

    printf() 返回值

返回值:傳輸到輸出流(顯示器)的字符數,若出現輸出錯誤或編碼錯誤(對於字符串和字符轉換指定符)則爲負值

返回類型:int

使用場景:檢查輸出錯誤。(看輸出的字符數是否正確)

#include<stdio.h>

int main(void) {

   int count;
   
   count = printf("Hello!\n");

   printf("%d\n", count);

   return 0;
}

輸出:

Hello!
7

打印較長字符串

允許的換行方式:

printf("Hello %s\n", 
       XiaoHuang);//爲了讓讀者知道該行未完,可以使用縮進

錯誤的換行方式:

printf("Hello 
       %s\n", XiaoHuang);

如果想在雙引號括起來的格式字符串中換行,應該這樣寫:

  1. printf("Hello");
    printf (" %s\n", XiaoHuang);
    
  2. printf("Hello\
    %s\n", XiaoHuang);
    
  3. printf("Hello"
    	" %s\n", XiaoHuang);// ANSI C
    

方法1:使用多個 printf 語句

方法2:在要換行的地方加上反斜槓( \ )來斷行。但是,下一行的代碼必須從該行最左端開始,不然輸出會包含你所縮進的空白字符。

方法3:ANSI C 引入的字符串連接。C 編譯器會將多個字符串看作一個字符串。

scanf() 函數

我們從鍵盤輸入的都是文本,因爲鍵盤只能生成文本字符:字符,數字和標點符號。如果要輸入整數 2014,就要鍵入2,0,1,4.如果要將其存儲爲數值而不是字符串,程序就必須要把字符依次轉換成數值,這就是 scanf() 要做的。

scanf() 把輸入的字符串轉換成整數,浮點數,字符和字符串,而 printf() 正好與之相反,把整數,浮點數,字符,字符串轉換成顯示在屏幕上的文本。

scanf() 與 printf() 類似,也要使用 格式字符串 和 參數列表。scanf() 中的格式字符串表明字符輸入流的目標數據類型。兩個函數的主要區別在於參數列表中。printf() 函數使用變量,常量和表達式,而 scanf() 函數使用指向變量的指針​(▶️)。這裏不需要了解指針,只需要記住一下簡單的兩條:

用 scanf 讀取

  • 基本變量類型的值,在變量名前加上一個&
  • 把字符串讀入數組中,不要使用 &

下面的程序演示了這兩條規則:

input.c —— 何時使用 &

#include<stdio.h>
int main(void){
    
    int age;
    float assets;
    char pets[30];//字符數組,存放字符串
    
    printf("Enter you age, assets and you favorite pet.\n");
    scanf("%d %f", &age, &assets); // 這裏要用 &
    scanf("%s", pets);// 字符數組不使用 &
      
    return 0;
}

⚠️

初學者在使用 scanf 時,在應該寫 & 的時候容易忽略 & ,所以每次使用 scanf 的時候一定要格外小心。通常情況下,必要的地方缺少 & 會讓程序崩潰(編譯器沒有警告),但是也有時候程序並不會崩潰,這時候找 bug 可能會讓你頭痛。


scanf 的 長度修飾符 和 轉換說明符 與 printf 幾乎相同。主要的區別如下:

  • 長度修飾符 :(可選項)對於 float 與 double 類型,printf() 的轉換說明都用 f; 而對於 scanf() ,float 保持不變,double 要在 f 前加長度修飾符 l ,即:lf例 1

    例 1:

    #include<stdio.h>
    
    int main(void) {
    	
    	double a = 3.0;
    
    	scanf("%lf", &a);
    	printf("%lf", a);
    	return 0;
    }
    
  • 轉換說明符%[集合]匹配集合中的任意序列;%[^集合]匹配非集合中的任意序列。例 2

    例 2:

    #include<stdio.h>
    
    int main(void) {
    	
    	char str[10];//字符串數組
    
    	scanf("%[123]", str);
    	printf("%s", str);
    
    	return 0;
    }
    //輸入:123456abc123
    //輸出:123
    
    int main(void) {
    	
    	char str[10];//字符串數組
    
    	scanf("%[^123]", str);
    	printf("%s", str);
    
    	return 0;
    }
    //輸入:abc4123a
    //輸出:abc4
    
  • 字符 *:(可選項)字符 * 出現意味着賦值屏蔽(assignment suppression): 讀入次數據項,但是不會將其賦值給對象。用 * 匹配的數據項不包含在 …scanf 函數返回的計數中。例 3

    例 3:

    #include<stdio.h>
    
    int main(void) {
    	
    	int a = 0;
    
    	scanf("%*d%d", &a);
    	printf("%d", a);
    
    	return 0;
    }
    輸入:1 2
    輸出:2
    
  • 最大字段寬度:(可選項)最大字段寬度限制了輸入項中的字符數量。如果達到最大值,那麼次數據項的轉換結束。轉換開始跳過的空白不計。例 4

    //輸入:1234 Hello
    //先猜測一下輸出
    #include<stdio.h>
    
    int main(void) {
    	
    	int a = 0;
    	char str[10];
    
    	scanf("%2d%3s", &a, str);
    	printf("%d %s", a, str);
    
    	return 0;
    }
    //輸出:12 34
    

進一步思探究 scanf()

在上面瞭解了 scanf 的基本情況後,我們進一步探究 scanf 函數。

上面的例 2,爲何只是輸出了 “123”, 我們明明還輸入了一組 123,爲什麼沒有輸出呢?

scanf 函數如果發生了 輸入失敗(沒有字符輸入)或 匹配失敗 (即輸入字符和格式串不匹配),那麼…scanf 會提前返回。返回就意味着這個 scanf 的讀入結束。

scanf 返回的又是什麼呢?

成功賦值的接收參數的數量(可以爲零,在首個接收用參數賦值前匹配失敗的情況下),或者若輸入在首個接收用參數賦值前發生失敗,則爲EOF(EOF 的值是 -1)。

在C程序中測試 scanf 函數的返回值的循環很普遍。例如,下面的循環逐一讀取一串整數,在首個遇到問題的符號處停止:

while(scanf("%d", &i) == 1){
    ...
}

對於 scanf 部分最開始的程序 input.c

如果我們這樣先輸入:

18 98.5
diandian

再這樣輸入:

  18
98.5


    diandian

如果你添加上 printf 語句輸出這三項,會發現,這兩種輸入的輸出是一樣的。

在尋找起始位置時,scanf 函數會忽略空白字符(white-space character,包括空格符,水平和垂直製表符,換頁符和換行符),但是%[ , %c, %n除外。例 5

例 5:

#include<stdio.h>
int main(void) {

	char ch = 'a';
	char str[10] = "hi";

	scanf("%c", &ch);
	scanf("%[123]", str);

	printf("%c %s", ch, str);


	return 0;
}
//輸入: b (輸入的是:空格 + b,然後按下回車鍵想接着輸入下一個 scanf)
//輸出: hi

這個例子除了證明了上面的結論,還說明了:

但是 scanf 函數會忽略最後的換行符,實際上它沒有讀取它,這個換行符時下一次 scanf 函數讀入的第一個字符。

scanf 函數遵循什麼規則來識別整數或浮點數呢?

在要讀入整數時,scanf 函數首先會尋找正號或負號,然後從讀入一個數字開始直到讀入一個非數字爲止。

當要求讀入浮點數時,scanf 函數首先會尋找正號或負號(可選),然後是一串數字(可能含有小數點),再後是一個指數(可選)。指數由一個字母e,可選的符號,一個或多個數字組成。

當 scanf 函數遇到一個不可能輸入當前項的字符時,它會把此字符“放回原處”,以便在掃描下一項或下一次調用 scanf 時再次讀入。思考下面(公認有問題的)4個數的排列:

1-20.3-4.0e3回車

然後我們用這個 scanf 函數來讀入:

scanf("%d%d%f%f", &i, &j, &x, &y);

scanf 會如何處理這組輸入呢?

  • %d :讀入 1
  • %d :讀入 -20
  • %f :讀入 .3 (當作 0.3 處理)
  • %f:讀入 剩下的輸入。但是不讀入最後的回車

使用 %s 轉換說明,scanf 會讀取除了空白字符以外的所以字符。scanf 跳過空白字符並開始讀入第一個非空白字符,保存非空白字符直到再遇到空白字符結束。這意味着,scanf 最多隻能讀取一個單詞。無法利用字段寬度使得 scanf 讀取多個單詞,scanf 會在字段寬度結束或遇到空白字符處停止。scanf 將字符串放入數組時,會在字符串序列末尾加上一個 \0

格式串中的普通字符

  • 空白字符:…scanf 函數格式串中的一個或多個連續的空白字符與輸入流中的零個或多個空白字符匹配。

    簡單說一下就是,格式串中有空格,輸入時你可以不寫空格或寫多個;格式串中有多個空格,輸入時你可以只寫一個空格。

  • 非空白字符:看個程序就明白了:

    #include<stdio.h>
    int main(void) {
    	
    	int i, j, k;
    	
    	printf("Enter a date: ");
    	scanf("%d - %d - %d", &i, &j, &k);
    	printf("date: %d - %d - %d", i, j, k);
    
    	return 0;
    }
    
    //輸入:
    Enter a date:  2020   - 2-22
    //輸出:
    date: 2020 - 2 - 22
    

    空格你可以隨便空,換行都可以隨便換,但是一定要打 ‘’-’’ 符號。

易混淆的 printf() 與 scanf()

  1. printf("%d", &i);
    

    輸出的並不是 i 的值 (而是 i 的地址的十進制數值)

  2. scanf("%d, %d", &i, &j);
    

    scanf 在第一個 %d 讀入一個整數後,試圖把逗號與輸入流中的下一個字符相匹配,如果這個字符不是 ,,那 scanf 就會終操作,而不再讀取變量 j 的值。

  3. scanf("%d\n", &i);
    

    printf 函數中經常有 \n ,但是如果在 scanf 格式串結尾放一個 \n 通常會引發你預期之外的問題。

    對於 scanf 函數來說,\n 等同於空格,那麼 scanf 就會在流中尋找空白字符,但是我們上面說過,scanf 格式串中的空白字符會與 輸入流中的零個或多個空白字符匹配。所以當你輸入完成後按下回車,這個回車會與 scanf 中的 \n 匹配,之後你無論打多少回車都不會使 scanf 結束,除非你輸入一個非空字符,使 scanf 因匹配失敗而退出。

參考資料:《C Primer Plus》《C語言程序設計:現代方法》


本文GitHub已收錄,所有教學和練習代碼都會上傳上去。

https://github.com/hairrrrr/C-CrashCourse

如果對你有幫助,請點一個 star⭐️ 呦~ ​ 感謝!💌

以上就是本次的內容。

如果文章有錯誤歡迎指正和補充,感謝!

最後,如果你還有什麼問題或者想知道到的,可以在評論區告訴我呦,我可以在後面的文章加上你們的真知灼見👁。

關注我,看更多幹貨!

我是程序圓,我們下次再見。🍂


  1. 任何編程語言在處理無關事務時都是低級語言。 ↩︎

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章