C語言 輸入、輸出(input/output I/O)

2022/07/07閱讀時間約 28 分鐘

前言

  在做程式的過程中,總會遇到需要讓使用者輸入內容,加以運算操作,最後輸出結果的事情吧,那今天要來介紹輸入與輸出,希望大家喜歡。

輸出

printf

  利用格式控制字元進行變數引用、格式化等的輸出。
printf("這裡是輸出示範 %d\n",a);
1、在括弧當中,雙引號括弧住的是你打算輸出的內容
2、"%"代表準備引用變數,下方有輸出控制字元表
3、變數必須在""外頭,並且用","做區隔
4、"\"為轉義字元,常用的有換行、換格,下方也有轉義字元表
5、後方的變數也可以是運算,如:
printf("%d",1+1);
6、printf可以使用多個變數、多個型態,如:
printf("%s %d","早安",1);
7、變數與""當中的%對應是由左而右,如:
printf("%d %d",1,2);
輸出出來的也是由左而右對應的:
1 2
計算方式
  printf中有多個變數時,雖然就輸出的結果來看,變數的對應是由左而右,這是正確的。但其實,裡面的計算卻是從右到左,不信的話我們來看:
#include 
 
int main(void) {
  int a = 1;
  printf("%d,%d,%d\n",a,a+1,a=a+2);
  printf("%d",a);
}
--------------------------------結果---------------------------------
3,4,3
3
  很奇怪對吧,照理來說應該是1,2,3才對,而且a的值居然變成3了,怎麼會是這樣呢?我們來看一下整個流程表。
1、首先計算方式是由左至右,所以是從a=a+2開始
2、a=a+2=3 這個過程中由於是"a=",因此實際上在這邊a就已經重新賦值了所以目前來
說=3,但這個時候第三個%d對應到的是變數a喔,並不是直接把3給%d
3、a+1=4,在這邊由於並不是"a=",而是單純的計算,所以會直接把4賦值給第二個%d
但a目前依舊是=3唷,a+1只是運算,並沒有重新賦值
4、a=3不做變動,且這時如同第2點,是將變數a給第一個%d,不是直接給值
5、稍微整理一下就會變成這樣,3,4,a
6、最後輸出的時候,最後的a就會賦值4給第一個以及第三個%d摟
7、從中我們可以學到,只要是"a="的,或是同樣直接對應到a的,會先給變數而已,等到
全部做完之後,才會把a目前的值給%d。反之沒有"a="的,則是會直接把當下計算到的值,
並給予%d
  稍微理解流程後,我們來看看這個:
#include 
 
int main(void) {
  int a = 1;
    printf("%d,%d,%d,%d\n",a+3,a=a+2,++a,a++);
    printf("%d",a);
}
--------------------------------結果---------------------------------
8,5,5,1
5
--------------------------------流程---------------------------------
1、首先來區分a++跟++a的差別
在賦值的情況下,也就是假設b=a++或b=++a的情況下
b=a++會先讓b得到a的值,然後a自己本身再+1
b=++a則是a自身先+1,再把a現在的值給b
所以我們可以很明顯看出,就是由左而右的概念,先遇到變數就是先給變數,先遇到
++則是先自身+1。
2、依照剛剛的道理我們來解析(a+3,a=a+2,++a,a++)
3、由右而左的順序,首先a++如剛剛所說,會直接賦值給%d,也就是把1給%d,然後a再
自身+1。此時a=1+1=2,且第四個%d為1
4、++a也如同剛剛所說,會先自身+1,然後再把a給%d。注意喔,不是給值,而是給變數
a。此時a=2+1=3,且第三個%d為a
5、a=a+2,如第一個例子所說,此時a=3+2=5,且第二個%d為a
6、a+3,也如第一個例子所說,因為只是運算,所以a依舊是5不變,而第一個%d為a+3
=8,所以第一個%d為8
7、稍微整理一下就是(8,a,a,1),且目前a=5
8、最後把a賦值給第二、三個%d,輸出後便是8,5,5,1
9、最後我們可以得出結論:
  a++會賦值給%d
  ++a會給%d變數a
  a=a...會給%d變數a
  a...會賦值給%d

格式控制字元、格式控制符(format char)

前言

  在做輸入輸出時,一定會有些格式上的問題,比方說哪裡要換行啊,哪裡要縮排等等的,而格式控制字元就是為此存在。

輸出控制字元、輸出控制符

%h
  上表中只要是整數相關,不論任何進位都可以在前+h,表示為short類型。
%l
  上表中只要是整數相關,不論任何進位都可以在前+l,表示為long類型。
%ll   
  上表中只要是整數相關,不論任何進位都可以在前+ll,表示為long long類型。    
%L
  上表中只要是浮點數相關,不論任何進位都可以在前+L,表示為long double類型。
#include "stdio.h"
int main(void){
  char a = 'a';
  printf("%%c 的結果: %c\n",a);//字元
  char b[15] = "想睡一下:(";
  printf("%%s 的結果: %s\n",b);//字串、字元陣列
  int c = 10;
  printf("%%d 的結果: %d\n",c);//整數
  printf("%%u 的結果: %u\n",c);//無號整數
  printf("%%o 的結果: %o\n",c);//無號8進位整數
  printf("%%x 的結果: %x\n",c);//無號16進位整數,1~f
  printf("%%X 的結果: %X\n",c);//無號16進位整數,1~F
  float d = 0.01;
  printf("%%f 的結果: %f\n",d);//浮點數
  printf("%%e 的結果: %e\n",d);//e格式浮點數;
  printf("%%E 的結果: %E\n",d);//E格式浮點數;
  float e = 0.000001;
  printf("%%F 格式: %g\n%%e 格式: %g\n",d,e);//%f與%e較短者
  printf("%%p 的結果: %p\n",&a);//變數位址(指標)
  printf("%% 的結果: %%");//輸出%這個字
}
--------------------------------結果---------------------------------
%c 的結果: a
%s 的結果: 想睡一下:(
%d 的結果: 10
%u 的結果: 10
%o 的結果: 12
%x 的結果: a
%X 的結果: A
%f 的結果: 0.010000
%e 的結果: 1.000000e-02
%E 的結果: 1.000000E-02
%F 格式: 0.01
%e 格式: 1e-06
%p 的結果: 000000ab543ffcf3
%% 的結果: %

轉義字元、轉義符

#include "stdio.h"
int main(void){
  printf("\a \n");//響鈴
  printf("\\b 的結果: 123\b \n\n");//倒退
  printf("\\f 的結果: 12\f34 \n\n");//換頁
  printf("\\n 的結果: 12\n\34 \n\n");//換行
  printf("\\t 的結果: 12\t34 \n\n");//水平製表(tab鍵按一下)
  printf("\\v 的結果: 12\v34 \n\n");//垂直製表
  
  printf("\\\' 的結果: \' \n\n");//輸出'
  
  printf("\\\" 的結果: \" \n\n");//輸出"
  
  printf("\\\\ 的結果: \\ \n\n");//輸出\;
}
--------------------------------結果---------------------------------
 
\b 的結果: 12 
\f 的結果: 12
34
\n 的結果: 12
\t 的結果: 12   34
\v 的結果: 12
       34
\' 的結果: '
\" 的結果: "
\\ 的結果: \
  注意!有一些編輯器是無法執行某些符號,比如\a不會響、\v只會下一行像\n一樣。

格式控制字元、格式控制符

  顧名思義,格式控制字元就是在控制變數的格式樣貌,比如常見的靠左對齊啊,或是輸出限定幾位的數字等等。接著,以下的內容皆使用 %d 作為示範。
#include "stdio.h"
int main(void){
  int a = 10;
  printf("-靠左對齊: %-10d\n",a);//在限定10格的輸出中靠左對齊
  printf("+顯示正負號: %+d\n",a);//顯示正負號
  printf(" 正數顯示空格: % 10d\n",a);//正數顯示空格,負數顯示-
  printf("x.x規定格數: %5.2d\n",a);//規定正數與小數格數
  //正數不夠以空格顯示,小數則是顯示更精度,但不一定是0
  //點我參照浮點數,理解精度問題
  printf("0用0補不足位數: %0-靠左對齊: 10        
+顯示正負號: +10
 正數顯示空格:         10
x.x規定格數:    10
0用0補不足位數: 000000001010d",a);//當不足規定位數時,以0去補
}
--------------------------------結果---------------------------------
-靠左對齊: 10        
+顯示正負號: +10
 正數顯示空格:         10
x.x規定格數:    10
0用0補不足位數: 0000000010
  有些人可能會覺得為什麼對齊只有靠左對齊,但其實C語言默認就是靠右對齊了,至於置中則是得靠自行去計算左方的空格數。如:
#include "stdio.h"
int main(void){
  printf("%6d\n",111111);
  printf("%5d\n",1111);
  printf("%4d\n",11);
}
--------------------------------結果---------------------------------
111111
 1111
  11

輸入

scanf

  首先scanf會先去緩衝區中讀取資料,倘若沒有資料則會等待使用者輸入資料,並利用前面講過的輸出控制字元,將使用者輸入的字轉為指定的類型,找到負責接收的變數位置,賦予該值。聽不懂也沒關係,我們先來看程式碼。
#include "stdio.h"
int main(void){
  int a;
  scanf("%d",&a);//%d 是整數,所以後面的變數也要是整數類型喔!
  printf("這是使用者輸入的值: %d",a);
}
--------------------------------結果---------------------------------
123//我輸入的值
這是使用者輸入的值: 123
  在scanf當中,我們在""中間所使用的輸出控制字元來表示我們希望的變數類型。當我輸入123之後,電腦就會立刻偵測這個123並轉化為整數型態,也就是%d。接著把123這個值,給予我所指定的 a 變數。(剛剛提到的緩衝區,我們留到後面在說)
  至於 a 前面的&符號,則是表示顯示出 a 變數在記憶體中的位置,這樣子電腦就能決定好值的類型後,直接透過 &a 從記憶體找出 a 這個變數,最後再給它123的值。
注意!
△千萬不要在scanf的""當中,亂寫一些非輸出控制字元的字。
  比方說:scanf("這是使用者輸入的值: %d",&a),這樣是不會讓 a 在輸出的時候,就自帶這些字的喔!反而還會讓使用者在輸入字的時候,要跟上面的一模一樣才不會出錯,這樣多此一舉不如只寫有意義的%d就好。
△區隔問題
  倘若今天要輸入多個變數時,我們僅需要以","做為區隔即可,但在""當中的輸出控制字元,就千萬不要用","隔開唷!你可以使用空格做為美觀的區隔,但用逗號的話,只會導致使用者在輸入時也要強迫要用逗號區隔。如以下程式碼:
#include "stdio.h"
int main(void){
  int a,b,c;
  scanf("%d%d%d",&a,&b,&c);
  printf("這是使用者輸入的值: %d %d %d",a,b,c);
}
--------------------------------結果---------------------------------
1 4 5
這是使用者輸入的值: 1 4 5
△使用者輸入
  在要求需要輸入多個變數的情況下,使用者僅需要以空格或是輸入、回車鍵(enter keyboard)、水平製表、跳格鍵(tab keyboard)作為變數間的區隔即可。如:
(以上方程式碼作為例子)
--------------------------------結果---------------------------------
1 4 5
這是使用者輸入的值: 1 4 5
--------------------------------或是---------------------------------
1
4
5
這是使用者輸入的值: 1 4 5
△緩衝區問題
  這就得更深的講到scanf的原理了,首先scanf並不是直接把我們輸入的值給予變數,而是會先去搜尋緩衝區的資料。就有點像是我們輸入字之後,它會先跑到一個叫做緩衝區的地方待著,一旦程式碼遇到scanf就會來到這個緩衝區找資料。如果緩衝區沒有資料的時候,才會等待使用者輸入新的資料。乍看之下好像沒甚麼問題,但請看看這段程式碼:
# include 
int main(void){
  int a;
  char b;//字元,儲存ascii小於128的字
  printf("請輸入一個整數: ");
  scanf("%d",&a);
  printf("a 的值: %d\n",a);
  printf("請輸入一個字元: ");
  scanf("%c",&b);
  printf("b 的值: %c",b);
}
--------------------------------結果---------------------------------
請輸入一個整數: 1
a 的值: 1
請輸入一個字元: b 的值:
  我們可以看到a變數是正常存取以及輸出的,但b是怎樣?照理來說我應該是要可以輸入一個字元的,但這邊是直接輸入完a就結束了。其實,根據我們剛剛所講,輸入的字會先存進緩衝區,所以當我輸入1並按下enter之後,緩衝區就會有了 "1\n" 這些字。你沒看錯,因為為了表示輸出結束我必須按下enter,所以也就會連\n(換行)也一起存了進去。
  接著,第一個scanf會以\n為間隔,將前面的字串轉為%d的整數,所以1被賦值給了a,也就是說\n被獨留下來,導致緩衝區還有資料,也因此第二個scanf才會直接把\n給了b,而不是等待使用者輸入。
  那這個問題其實很好解決,我們只要把程式碼改成這樣:
# include 
int main(void){
  int a;
  char b;
  printf("請輸入一個整數: ");
  scanf("%d",&a);
  printf("a 的值: %d\n",a);
  printf("請輸入一個字元: ");
  scanf(" %c",&b);//差別在於%c前面多了一個空格
  printf("b 的值: %c",b);
}
--------------------------------結果---------------------------------
請輸入一個整數: 5
a 的值: 5
請輸入一個字元: 6
b 的值: 6
--------------------------------或是---------------------------------
#include 
int main(void) {
  int a;
  char b;
  printf("請輸入一個整數: ",a);
  scanf("%d",&a);
  printf("a的值為: %d\n",a);
  b = getchar();//先把\n拿走,因此緩衝區會清空,scanf就會等使用者輸入了
  
  printf("請輸入一個字元: ",a);
  scanf("%c",&b);
  printf("b的值為: %c\n",b);
}
--------------------------------結果---------------------------------
請輸入一個整數: 6
a的值為: 6
請輸入一個字元: 6
b的值為: 6
  第一個的原理在於說,sanf中的空白符(空格、換格、\n的換行)會吃掉緩衝區所有的空白符,直到遇到非空白符才會停止。倘若到最後緩衝區都空了,也依舊沒有非空白符,則會等待使用者輸入。
  以上面為例,淺談scanf及緩衝區的流程為:
1、使用者輸入6
2、電腦緩衝區取得6\n
3、scanf把6轉為%d,並賦值給a變數,緩衝區只剩\n
4、第二個scanf讀取到空格後,開始吃掉所有空白符
5、\n被吃掉,緩衝區是空的
6、因為是空的,所以等待使用者輸入字元
7、輸入6,電腦讀取6並轉為%c,賦值給變數b
  第二個的方法則很簡單,getchar()會直接讀取緩衝區中的一個字元,倘若輸入為6 (數字6以及一個空格),則會產生這種結果:
(以上方第二個方法的getchar為例)
--------------------------------結果---------------------------------
請輸入整數: 6 (6+1個空格)
a的值為: 6
請輸入整數: b的值為:
  因為getchar只會讀取一個字元,導致它只取走一個空格,緩衝區仍剩下\n,致使b直接存取了\n。淺談流程如下:
1、使用者輸入6 (6+1個空格)
2、電腦緩衝區取得6 \n

3、scanf把6轉為%d,並賦值給a變數,緩衝區只剩 \n
4、getchar()讀取了1個空格,緩衝區只剩\n
5、電腦讀取\n並轉為%c,賦值給變數b

gets

  讀取所有緩衝區的字元,直到遇到換行符號,並化為字串(包含換行符號一起帶走,但輸出不會換行)給予指定變數。過程中倘若遇到空格或是製表符號、跳格符號,也會一起化為字串,不會因此停止讀取。
#include 
int main(void) {
  char b[50];
  printf("請輸入整數: ",a);
  gets(b);
  printf("b的值為: %s\n",b);
}
--------------------------------結果---------------------------------
請輸入字串: 我好餓
b的值為: 我好餓
 

gets與scanf差異

△分隔符號
  對於scanf而言,一遇到空白字符就等於一個分隔。而gets則是指遇到換行才會結束讀取,即使是空格或是換格符號,也會做為字串繼續讀取。
△開頭遇換行符號
  倘若緩衝區目前是"\n我好餓",一旦讀取之後,gets會直接判斷為\n而結束讀取。scanf則不一樣,倘若它存的是%s的字串,會忽略第一次\n,進而讀取後面的字串。所以在這個情況下,gets不能取得"我好餓",而scanf可以。程式碼如下:
#include 
 
int main(void) {
    int a;
    char b[100];
    printf("請輸入一個整數: ");
    scanf("%d", &a);//只取走1,遺留\n於緩衝區
    printf("a的值為: %d\n",a);
    printf("請輸入一串字: ");
    gets(b);//一遇到\n直接結束存取
    printf("b的值為: %s\n",b);
}
--------------------------------結果---------------------------------
請輸入一個整數: 1 (此時緩衝區為"1\n")
a的值為: 1
請輸入一串字: b的值為:
--------------------------------比較---------------------------------
#include 
 
int main(void) {
    int a;
    char b[100];
    printf("請輸入一個整數: ");
    scanf("%d", &a);//只取走1,遺留\n於緩衝區
    printf("a的值為: %d\n",a);
    printf("請輸入一串字: ");
    scanf("%s", &b);//忽視掉第一個\n,則此時緩衝區為空,因此要求使用者輸入
    printf("b的值為: %s\n",b);
}
--------------------------------結果---------------------------------
請輸入一個整數: 1
a的值為: 1
請輸入一串字: 我好餓
b的值為: 我好餓 (正常輸入與輸出)
△結尾的換行符號
  當使用者輸入了一串字"我好餓",並按下enter後,此時進到緩衝區為"我好餓\n"。在經過讀取後,gets會連同\n一起讀取,不留下\n在緩衝區的。而scanf則只會讀取"我好餓",遺留\n在緩衝區當中。程式碼如下:
#include 
 
int main(void) {
    char a[100];
    char b;
    printf("請輸入一串字: ");
    scanf("%s", &a);
    printf("a的值為: %s\n",a);
  printf("請輸入一個字元: ");
    scanf("%c", &b);
    printf("b的值為: %c\n",b);
}
--------------------------------結果---------------------------------
請輸入一串字: 我好餓
a的值為: 我好餓
請輸入一個字元:
(b無法輸入是因為,緩衝區中還有一個\n,所以直接讓b存取了)
--------------------------------比較---------------------------------
#include
 
int main(void) {
    char a[100];
    char b;
    printf("請輸入一串字: ");
    gets(a);//這邊改成gets()之後,\n就不會遺留緩衝區,因此緩衝區為空
    printf("a的值為: %s\n",a);
  printf("請輸入一個字元: ");
    scanf("%c", &b);//因為緩衝區為空,導致scanf要求使用者輸入
    printf("b的值為: %c\n",b);
}
--------------------------------結果---------------------------------
請輸入一串字: 我好累:(
a的值為: 我好累:(
請輸入一個字元: 1
b的值為: 1
為什麼會看到廣告
8會員
18內容數
這裡是來自 高科大 資管系二年級的學生,希望能在學習的過程中,也分享這些知識給大家。
留言0
查看全部
發表第一個留言支持創作者!