跳轉到內容

C 語言入門/C 控制檯 I/O

來自華夏公益教科書,自由的教科書

本章涵蓋控制檯(鍵盤/顯示器)和檔案 I/O。您已經看到了一個控制檯 I/O 函式“printf()”,還有其他幾個。C 語言對檔案 I/O 有兩種不同的方法,一種基於類似於控制檯 I/O 的庫函式,另一種使用“系統呼叫”。這些主題將在下面詳細討論。

通常情況下,控制檯 I/O 指的是與計算機的鍵盤和顯示器的通訊。然而,在大多數現代作業系統中,鍵盤和顯示器只是預設的輸入和輸出裝置,使用者可以輕鬆地將輸入重定向到例如檔案或其他程式,並將輸出重定向到例如序列 I/O 埠。

   type infile > myprog > com

程式本身“myprog”不知道其中的區別。該程式使用控制檯 I/O 來簡單地讀取其“標準輸入 (stdin)” - 這可能是鍵盤、檔案轉儲或其他程式的輸出 - 並在其“標準輸出 (stdout)”上列印 - 這可能是顯示器、印表機或其他程式或檔案。程式本身既不知道也不在乎。

控制檯 I/O 需要宣告

   #include <stdio.h>

有用的函式包括

   printf()      Print a formatted string to stdout.
   scanf()       Read formatted data from stdin.
   putchar()     Print a single character to stdout.
   getchar()     Read a single character from stdin.
   puts()        Print a string to stdout.
   gets()        Read a line from stdin.

基於 Windows 的編譯器還具有一個可選的控制檯 I/O 函式庫。這些函式需要宣告

   #include <conio.h>

三個最常用的 Windows 控制檯 I/O 函式是

   getch()    Get a character from the keyboard (no need to press Enter).
   getche()   Get a character from the keyboard and echo it.
   kbhit()    Check to see if a key has been pressed. 

正如之前解釋的那樣,“printf()”函式列印一個包含格式化資料的字串

   printf( "This is a test!\n" );

- 這可以包含變數的內容

   printf( "Value1:  %d   Value2:  %f\n", intval, floatval );

可用的格式程式碼是

   %d    decimal integer
   %ld   long decimal integer
   %c    character
   %s    string
   %e    floating-point number in exponential notation
   %f    floating-point number in decimal notation
   %g    use %e and %f, whichever is shorter
   %u    unsigned decimal integer
   %o    unsigned octal integer
   %x    unsigned hex integer

對特定資料型別使用錯誤的格式程式碼會導致奇怪的輸出。可以使用修飾符程式碼獲得更多控制;例如,可以包含一個數字字首來指定最小欄位寬度

   %10d

這指定了最小欄位寬度為十個字元。如果欄位寬度太小,將使用更寬的欄位。新增一個減號

   %-10d

- 會導致文字左對齊。

還可以指定一個數字精度

   %6.3f

這指定了六個字元寬的欄位中三位精度。

也可以指定一個字串精度,以指示要列印的最大字元數。例如

   /* prtint.c */

   #include <stdio.h>

   int main(void)
   {
     printf( "<%d>\n", 336 );
     printf( "<%2d>\n", 336 );
     printf( "<%10d>\n", 336 );
     printf( "<%-10d>\n", 336 );
     return 0;
   }

這將列印

   <336>
   <336>
   <       336>
   <336       >

類似地

   /* prtfloat.c */

   #include <stdio.h>

   int main(void)
   {
     printf( "<%f>\n", 1234.56 );
     printf( "<%e>\n", 1234.56 );
     printf( "<%4.2f>\n", 1234.56 );
     printf( "<%3.1f>\n", 1234.56 );
     printf( "<%10.3f>\n", 1234.56 );
     printf( "<%10.3e>\n", 1234.56 );
     return 0;
   }

- 列印

   <1234.560000>
   <1.234560e+03>
   <1234.56>
   <1234.6>
   <  1234.560>
   < 1.234e+03>

最後

   /* prtstr.c */

   #include <stdio.h>

   int main(void)
   {
     printf( "<%2s>\n", "Barney must die!" );
     printf( "<%22s>\n", "Barney must die!" );
     printf( "<%22.5s>\n", "Barney must die!" );
     printf( "<%-22.5s>\n", "Barney must die!" );
     return 0;
   }

- 列印

   <Barney must die!>
   <      Barney must die!>
   <                 Barne>
   <Barne                 >

為了方便起見,第 2 章中列出的特殊字元表在此重複。這些字元可以嵌入到“printf”字串中

   '\a'     alarm (beep) character
   '\p'     backspace
   '\b'     backspace
   '\f'     formfeed
   '\n'     newline
   '\r'     carriage return
   '\t'     horizontal tab
   '\v'     vertical tab
   '\\'     backslash
   '\?'     question mark
   '\''      single quote
   '\"'     double quote
   '%%'     percentage
   '\0NN'   character code in octal
   '\xNN'   character code in hex
   '\0'     null character

“scanf()”函式使用類似於“printf”的語法讀取格式化資料,只是它需要指標作為引數,因為它必須返回值。例如

   /* cscanf.c */

   #include <stdio.h>

   int main(void)
   {
     int val;
     char name[256];
   
     printf( "Enter your age and name.\n" );
     scanf( "%d %s", &val, name ); 
     printf( "Your name is: %s -- and your age is: %d\n", name, val );
     return 0;
   }

由於字串的名稱本身就是一個指標,“name”前面沒有“&”。輸入欄位由空格(空格、製表符或換行符)分隔,儘管可以包含一個計數,例如“%10d”,來定義特定的欄位寬度。格式程式碼與“printf()”相同,除了

  • 沒有“%g”格式程式碼。
  • “%f”和“%e”格式程式碼的工作方式相同。
  • 有一個“%h”格式程式碼用於讀取短整數。

如果格式程式碼中包含字元,“scanf()”將讀取這些字元並丟棄它們。例如,如果上面的示例修改如下

   scanf( "%d,%s", &val, name );

- 那麼“scanf()”將假定兩個輸入值是逗號分隔的,並在遇到逗號時將其吞掉。

如果一個格式程式碼前面有一個星號,資料將被讀取並丟棄。例如,如果將示例更改為

   scanf( "%d%*c%s", &val, name );

- 那麼如果兩個欄位由“:”分隔,該字元將被讀取並丟棄。

當輸入終止時,“scanf()”函式將返回 EOF(一個“int”,在“stdio.h”中定義)。

“putchar()”和“getchar()”函式處理單個字元 I/O。例如,以下程式一次接受來自標準輸入的字元

   /* inout.c */

   #include <stdio.h>

   int main (void)
   {
     unsigned int ch; 
   
     while ((ch = getchar()) != EOF)
     {
       putchar( ch ); 
     }
     return 0;
   }

“getchar”函式返回一個“int”,並在 EOF 時終止。請注意,C 語言允許程式以一種簡潔的方式獲取值並在同一表示式中對其進行測試,這是處理迴圈的特別有用的特性。

關於單個字元 I/O 的一個警告:如果程式正在從鍵盤讀取字元,大多數作業系統不會在使用者按下“Enter”鍵之前將字元傳送給程式,這意味著無法以這種方式執行單個字元鍵盤 I/O。

上面的簡短程式是字元模式文字“過濾器”的核心,它是一個可以在標準輸入和標準輸出之間執行一些轉換的程式。這樣的過濾器可以用作構建更復雜應用程式的元素

   type file.txt > filter1 | filter2 > outfile.txt

以下過濾器將輸入中每個單詞的首字母大寫。該程式作為一個“狀態機”執行,使用一個可以設定為不同值或“狀態”的變數來控制其執行模式。它有兩個狀態:SEEK,它在尋找第一個字元,和 REPLACE,它在尋找一個單詞的結尾。

在 SEEK 狀態下,它掃描空格(空格、製表符或換行符),並回顯字元。如果它找到一個列印字元,它將把它轉換為大寫,並進入 REPLACE 狀態。在 REPLACE 狀態下,它將字元轉換為小寫,直到遇到空格,然後返回 SEEK 狀態。

該程式使用“tolower()”和“toupper()”函式進行大小寫轉換。這兩個函式將在下一章中討論。

   /* caps.c */

   #include <stdio.h>
   #include <ctype.h>

   #define SEEK 0
   #define REPLACE 1

   int main(void)
   {
     int ch, state = SEEK;
     while(( ch = getchar() ) != EOF )
     {
       switch( state )
       {
       case REPLACE:
         switch( ch )
         {
         case ' ':
         case '\t':
         case '\n':   state = SEEK;
                      break;
         default:     ch = tolower( ch );
                      break;
         }
         break;
       case SEEK:
         switch( ch )
         {
         case ' ':
         case '\t':
         case '\n':   break;
         default:     ch = toupper( ch );
                      state = REPLACE;
                      break;
         }
       }
       putchar( ch );
     }
     return 0;
   }

“puts()”函式類似於簡化的“printf()”,沒有格式程式碼。它列印一個自動以換行符結尾的字串

   puts( "Hello world!" );

“fgets()”函式特別有用:它讀取一個以換行符結尾的文字行。它對輸入的限制比“scanf()”少得多

   /* cgets.c */

   #include <stdio.h>
   #include <string.h>

   #include <stdlib.h>

   int main(void)
   {
     char word[256], 
          *guess = "blue\n";
     int i, n = 0;

     puts( "Guess a color (use lower case please):" );
     while( fgets(word, 256, stdin) != NULL )
     {
       if( strcmp( word, guess ) == 0 )
       {
          puts( "You win!" );
          break;
       }
       else
       {
          puts( "No, try again." );
       }
     }
     return 0;
   }

該程式包含“strcmp”函式,該函式執行字串比較,並在匹配時返回 0。這個函式將在下一章中更詳細地描述。

這些函式可以用來實現對文字行而不是字元進行操作的過濾器。以下是對這些過濾器的核心程式

   /* lfilter.c */

   #include <stdio.h>

   int main (void)
   {
     char b[256];
     while (( fgets(b, 256, stdin) ) != NULL )
     {
       puts( b ); 
     }
     return 0;
   }

當輸入終止或出錯時,“fgets()”函式返回“stdio.h”中定義的 NULL。

基於 Windows 的控制檯 I/O 函式“getch()”和“getche()”的工作方式與“getchar()”非常類似,只是“getche()”會自動回顯字元。

“kbhit()”函式非常不同,因為它只指示是否按下了鍵。如果按下了鍵,它將返回一個非零值,如果沒有按下,它將返回零。這允許程式輪詢鍵盤以獲取輸入,而不是掛起鍵盤輸入並等待事件發生。如前所述,這些函式需要“conio.h”標頭檔案,而不是“stdio.h”標頭檔案。

華夏公益教科書