第四章 字串和格式化輸入/輸出

Hans_Wang發表於2021-12-19

第四章 字串和格式化輸入/輸出

1 前導程式

#include <stdio.h>
#include <string.h>
#define DENSITY 62.4

int main(){
    float weight, volume;
    int size, letters;
    char name[40];

    printf("Hi! What's your first name?\n");
    scanf("%s", name);
    printf("%s,What's your weight in pounds?\n", name);
    scanf("%f", &weight);
    size = sizeof(name);
    letters = strlen(name);
    volume = weight / DENSITY;

    printf("Well, %s, your volume is %2.2f cubic feet.\n", name, volume);
    printf("Also, your first name has %d leeters,\n", letters);
    printf("and we have %d bytes to store it.\n", size);

    return 0;

}
[root@hans ~]# ./talkback 
Hi! What's your first name?
Chirstine
Chirstine,What's your weight in pounds?
154
Well, Chirstine, your volume is 2.47 cubic feet.
Also, your first name has 9 leeters,
and we have 40 bytes to store it.

該程式包含以下新特性:

  • 用陣列儲存字串
  • 使用%s轉換說明來處理字串的輸入和輸出。注意scanf()中name沒有&字首,而weight有字首
  • 用C前處理器把字元常量DENSITY定義為62.4
  • 用C函式strlen()獲取字串的長度

2 字串簡介

字串是一個或多個字元的序列。

"Zing went the strings of my heart!"

雙引號不是字串的一部分,它只是告訴編譯器它括起來的是字串,正如單引號用於標識單個字元一樣。

2.1 char型別陣列和null字元

C語言中沒有專門用於儲存字串的變數型別,字串都被儲存在char型別的陣列中,陣列由連線的儲存單元組成。字串中的字元被儲存在相鄰的儲存單元中,每個單元儲存一個字元。

|Z|i|n|g| |w|e|n|t| |t|h|e| |s|t|r|i|n|g|s| |o|f| |m|y| |h|e|a|r|t|!|\0|

在陣列末尾位置的字元 \o 為空字元,C語言用它標記字串的結束,空字元不是數字0,它是非列印字元。其ASCII碼值是0,C中字串一定以空字元結束,這就意味著陣列的容量必須至少比待儲存字串的字元數多1.40個儲存單元的字串只能儲存39個字元,剩下一個位元組留給空字元。

陣列,可以以把陣列看作是一行連續的多個儲存單元。

image

2.2 使用字串

[root@hans ~]# cat praise1.c 
#include <stdio.h>
#define PRAISE "You are an extraordinary being."

int main(void){
    char name[40];
    printf("What's your name?");
    scanf("%s", name);
    printf("Hello, %s. %s\n", name, PRAISE);

    return 0;
}

[root@hans ~]# ./praise1 
What's your name?Angela Plains
Hello, Angela. You are an extraordinary being.

在列印時有兩個%s,第一個是name,第二個是PRAISE.但列印name時scanf()只讀取了Angela Plains中的Angela,它是遇到第1個空白(空格,製表符或換行符)時就不再讀取輸入。所以在讀到Angela Plains之間的空格時就停止了,根據%s轉換說明,scanf()只會讀取字串中的一個單詞,而不是一整句。

字串和字元:

字串常量“x”和字元常量‘x’不同,區別之一在於'x'是基本型別(char),而“X”是派生型別(char 陣列);區別之二是“x”實際上由兩個字元組成:

'X'和空字元\0.

image

2.3 strlen()函式

sizeof它以位元組為單位給出物件的大小。strlen()函式給出字串中的字元長度。

[root@hans ~]# cat praise2.c 
#include <stdio.h>
#include <string.h>
#define PRAISE "You are an extraordinary being."

int main(void){
    char name[40];

    printf("What's your name?");
    scanf("%s", name);
    printf("Hello, %s. %s\n", name, PRAISE);
    printf("Your name of %zd letters occupies %zd memory cells.\n", strlen(name), sizeof name);
    printf("The phrase of praise has %zd letters ", strlen(PRAISE)); 
    printf("and occupies %zd memory cells.\n", sizeof PRAISE );

    return 0;
}
[root@hans ~]# ./praise2 
What's your name?Serendipity Chance
Hello, Serendipity. You are an extraordinary being.
Your name of 11 letters occupies 40 memory cells.
The phrase of praise has 31 letters and occupies 32 memory cells.

sizeof運算子報告,name陣列有40個儲存單元,但strlen()得出來的結果是11.name陣列的第12個單元儲存空字元並未將其計入。

image

對了PRAISE,用strlen()得出的是字串中的字元數(包括空格和標點符號)。但sizeof把PRAISE字串末尾不可見的空字元也計算在內。是因為程式並未明確告訴計算機要給字串預留多少空間,所以它必須計算雙引號內的字元數。

C99和C11標準專門為sizeof運算子的返回型別新增了%zd轉換說明。

還有注意一點:上一章的sizeof使用了圓括號,但本例中沒有。何時使用圓括號取決於運算物件是型別還是特定量,運算物件是型別時圓括號必不可少,但對於特定量,可有可無。對於型別應寫成sizeof(char)sizeof(float);對於特定量可寫成 sizeof namesizeof 6.28.儘管如此,還是建議所有情況下都使用圓括號。

3 常量和C前處理器

有些時候程式中要使用常量,比如計算圓的周長。這裡3.14159代表著名的常量pi.

如何建立常量,方法之一是宣告一個變數,然後將該變數設定為所需的常量,但變數在程式中可能會無意間改變它的值,所以使用C語言提供的C前處理器。只需在程式頂部加一行:

# define PI 3.14159

編譯程式時所有的PI都會替換成3.14159,這個過程被稱為編譯時替換。在執行程式時,程式中的替換均已完成。通常這樣定義的常量也稱為明示常量。

格式:首先是#define ,接著是符號常量名(PI),然後是符號常量的值(3.14159)

#define NAME value (其中沒有=符號,而且末尾不用加分號)
NAME一般為大寫,這是C語言一貫傳統。

符號常量的命名規則與變數相同,可以使用大小寫字母、數字、和下劃線字元,首字元不能為數字。

image

[root@hans ~]# cat pizza.c 
#include <stdio.h>
#define PI 3.14159

int main(viod){
    float area, circum, radius;

    printf("What is the radius of your pizza?\n");
    scanf("%f",&radius);

    area = PI * radius * radius;
    circum = 2.0 * PI * radius;

    printf("Your basic pizza parameters are as follows:\n");
    printf("circumference = %1.2f, area = %1.2f\n", circum, area);


    return 0;

}

[root@hans ~]# ./pizza 
What is the radius of your pizza?
6.0
Your basic pizza parameters are as follows:
circumference = 37.70, area = 113.10

#define指令還可以定義字元和字串常量,前者使用單引號,後者使用雙引號。

#define BEEP '\a'
#define TEE 'T'
#define ESC '\033'
#define OOPS 'NOW you have done it!'
符號常量名後面的內容被用來替換符號常量。
/*錯誤的格式*/
#define TOES = 20 #這麼寫TOES為= 20 不是20.

3.1 const 限定符

C90標準新增了const關鍵字,用來限定一個變數為只讀。(用const型別限定符宣告的是變數,不是常量)

const int MONTHS = 12; //MONTHS在程式中不可更改。

3.2 明示常量

C標頭檔案limits.h和float.h分別提供了與整數型別和浮點型別大小限制相關的詳細資訊。每個標頭檔案都定義了一系列實現使用的明示常量。(明示常量相當於符號常量)

image

image

如何使用limits.h和float.h中的資料

[root@hans ~]# cat defines.c 
#include <stdio.h>
#include <limits.h>
#include <float.h>

int main(void){
    printf("Some number limits for this system\n");
    printf("Biggest int:%d\n", INT_MAX);
    printf("Smallest long long: %lld\n", LLONG_MIN);
    printf("One byte = %d bits on this system.\n", CHAR_BIT);
    printf("Largest double: %e\n", DBL_MAX);
    printf("Smallest normal float: %e\n", FLT_MIN);
    printf("float precision = %d digits\n", FLT_DIG);
    printf("float epsilon = %e\n", FLT_EPSILON);

    return 0;
}
[root@hans ~]# ./defines 
Some number limits for this system
Biggest int:2147483647
Smallest long long: -9223372036854775808
One byte = 8 bits on this system.
Largest double: 1.797693e+308
Smallest normal float: 1.175494e-38
float precision = 6 digits
float epsilon = 1.192093e-07

4 printf()和scanf()

printf()和scanf()它們是輸入/輸出函式,或簡稱I/O函式。能讓使用者可以與程式交流。

4.1 printf()函式

請求printf()函式列印資料的指令要與待列印資料的型別相匹配。如列印整數時使用%d,列印字元時用%c,這些符號被稱為轉換說明,它們指定了如何把資料轉成可顯示的形式。

轉換說明 輸出
%a,%A 浮點數、十六進位制數和p計數法(C99/C11)
%c 單個個字元
%d 有符號十進位制數
%e,%E 浮點數,e計數法
%f 浮點數,十進位制計數法
%g 根據數值不同自動選擇%f或%e,%e格式在指數小於-4或者大於等於精度時使用
%G 根據數值不同自動選擇%f或%E,%E格式在指數小於-4或者大於等於精度時使用
%i 有符號十進位制整數(與%d相同)
%o 無符號八進位制整數
%p 指標
%s 字串
%u 無符號十進位制數
%x 無符號十六進位制整數,使用十六進位制數0f
%X 無符號十六進位制整數,使用十六進位制數0F
%% 列印一個百分號
[root@hans ~]# cat printout.c 
#include <stdio.h>
#define PI 3.141593

int main(viod){
    int number = 7;
    float pies = 12.75;
    int cost = 7800;

    printf("The %d contestants ate %f berry pies.\n", number, pies);
    printf("The value of pi is %f. \n", PI);
    printf("Farewell! thou art too dear for my possessing, \n");
    printf("%c%d\n", '$', 2 * cost);

    return 0;
}
[root@hans ~]# ./printout 
The 7 contestants ate 12.750000 berry pies.
The value of pi is 3.141593. 
Farewell! thou art too dear for my possessing, 
$15600

printf()函式的格式

printf(格式字串,待列印項1,待列印項2,......)
格式字串是雙引號括起來的內容。

image

4.2 printf()的轉換說明修飾符

修飾符 含義
標誌 五種標誌(-、+、空格、非和0),可以不使用標記或使得多個標記,示例: “%-10d”
數字 欄位寬度的最小值。如果欄位不能容納要列印的數或者字串,系統會使用更寬的欄位示例: “%4d”
.數字 精度 對於%e,%E和%f轉換,是將要在小數點的右邊列印的數字的位數。對於%g和%G轉換,是有效數字的最大位數。對於%s轉換,是將要列印的字元的最大數目。對於整數轉換,是將要列印的數字的最小位數。如果必要,要使用前導0來達到位數。只使用"."表示其後跟隨一個0,所以%.f和%.0f相同。示例: “%5.2f”表示列印一個浮點數,它的欄位寬度為5個字元,小數點後有兩個數字
h 和整數轉換說明符一起使用,表示一個short int或unsigned short int型別數值。示例: “%hu”, “%hx”, “%6.4hd”
hh 和證照轉換說明符一起使用,表示一個signed char或unsigned char型別數值
j 和整數轉換說明符一起使用,表示一個intmax_t或uintmax_t值示例: “%jd”,"%8jx"
l 和整數轉換說明符一起使用,表示一個long int或unsigned long int型別值
ll 和整數轉換說明符一起使用,表示一個long long int或unsigned long long int型別值(C99)示例: “%lld”,"%8llu"
L 和浮點數轉換說明符一起使用,表示一個long double值示例: “%Lf”, “%10.4Le”
t 和整數轉換說明符一起使用,表示一個ptrdiff_t值(與兩個指標之間的差相對應的型別)(C99)。示例: “%td”, “%1ti”
z 和整數轉換說明符一起使用,表示一個size_t值(sizeof返回的型別)(C99)。示例: “%zd”,"%12zd"

printf()中的標記

標誌 意義
- 列印項左對齊,即,從欄位的左側開始列印該項。示例: “%-20s”
+ 有符號的值若為正,則顯示帶加號的符號;若為負,則顯示帶減號的符號。示例: “%+6.2f”
空格 有符號的值若為正,則顯示時帶前導空格(不顯示符號);若為負,則在前面顯示減號。+標誌會覆蓋一個空格。示例: “% 6.2f”
# 使用轉換說明的另一種形式。若為%o格式,則以0開始;若為%x和%X格式,則以0x或0X開始。對於所有的浮點形式,#保證了即使不跟任何數字,也列印一個小數點字元。對於%g和%G格式,它防止尾隨0被刪除。示例: “%#o”, “%#8.0f”, “%+#10.3e”
0 對於數值格式,用前導零代替空格填充欄位寬度。對於整數格式,如果出現-標誌或者指定了精度(對於整數)則忽略該標誌。示例: “%010d”, “%08.3f”

示例:

[root@hans ~]# cat width.c 
#include <stdio.h>
#define PAGES 959

int main(void){
    printf("*%d*\n", PAGES);
    printf("*%2d*\n", PAGES);  
    printf("*%10d*\n", PAGES);
    printf("*%-10d*\n", PAGES);

    return 0;
}
[root@hans ~]# ./width 
*959*
*959*   #轉換說明是%2d,其對應輸出結果應該是2欄位寬度,因為列印的整數有3位數字,所以欄位寬度自動擴大以符合整數的長度。
*       959*
*959       *

浮點格式示例:

[root@hans ~]# cat floats.c 
#include <stdio.h>

int main(void){

    const double RENT = 3852.99;
    printf("*%f*\n", RENT);
    printf("*%e*\n", RENT);
    printf("*%4.2f*\n", RENT);
    printf("*%3.1f*\n", RENT);
    printf("*%10.3f*\n", RENT);
    printf("*%10.3E*\n", RENT);
    printf("*%+4.2f*\n", RENT);
    printf("*%010.2f*\n", RENT);

    return 0;
}
[root@hans ~]# ./floats               
*3852.990000*
*3.852990e+03*
*3852.99*
*3853.0*
*  3852.990*
* 3.853E+03*
*+3852.99*
*0003852.99*

格式標記:

[root@hans ~]# cat flags.c
#include <stdio.h>

int main(void){
    printf("%x %X %#x\n",31, 31, 31);
    printf("**%d**% d**% d**\n", 42, 42, -42);
    printf("**%5d**%5.3d**%05d**%05.3d**\n", 6, 6, 6, 6);

    return 0;
}
[root@hans ~]# ./flags 
1f 1F 0x1f
**42** 42**-42**
**    6**  006**00006**  006**

字串格式:

[root@hans ~]# cat stringf.c 
#include <stdio.h>
#define  BLURB "Authentic imitation!"

int main(void){
    printf("[%2s]\n", BLURB);
    printf("[%24s]\n", BLURB);
    printf("[%24.5s]\n", BLURB);
    printf("[%-24.5s]\n", BLURB);  #.5告訴printf()只列印5個字元,-標記使得文字左對齊輸出

    return 0;

}
[root@hans ~]# ./stringf 
[Authentic imitation!]
[    Authentic imitation!]
[                   Authe]
[Authe                   ]

4.3 轉換說明的意義

轉換說明把二進位制格式儲存在計算機中的值轉換成一系列字元(字串)以便於顯示。

4.3.1 轉換不匹配

轉換說明應該與待列印值的型別相匹配。如:列印int型別的值,可以使用%d,%x,%o,列印double型別可使用%f,%e或%g

[root@hans ~]# cat intconv.c   #一些不匹配的整型轉換。
#include <stdio.h>
#define PAGES 336
#define WORDS 65618

int main(void){
    short num = PAGES;
    short mnum = -PAGES;
    
    printf("num as short and unsigned short: %hd %hu\n", num, num);
    printf("-num as short and unsigned short: %hd %hu\n", mnum, mnum);
    printf("num as int and char: %d %c\n", num, num);
    printf("WORDS as int, short, and char: %d %hd %c\n", WORDS, WORDS, WORDS);

    return 0;
}

[root@hans ~]# ./intconv 
num as short and unsigned short: 336 336
-num as short and unsigned short: -336 65200
num as int and char: 336 P
WORDS as int, short, and char: 65618 82 R

第一行,num對應轉換說明%hd和%hu輸出結果為336,沒有問題
第二行,mnum對應的轉換說明%u輸出結果卻為65200,並非期望的336.這是因為有符號short int 型別的值在我們的參考系統中的表示方式所致。首先,short int的大小是2位元組;其次,系統使用二進位制補碼來表示有符號整數。這種方法數字0~32767代表它們本身,而數字32768~65535則表示負數。其中65535表示-1,65534表示-2.以此類推。因此,-336表示65200.擬被解釋成有符號int時,65200代表-336,而被解釋成無符號Int時65200代表65200.一個數字可以被解釋成兩個不同的值。
第三行,演示了把一個大於255的值轉換成字元會發生什麼?在系統中short int是2位元組,char是1位元組,當pirntf()使用%c列印336時,它只會檢視儲存336的2位元組中的後1位元組。這種截斷相當於用一個整數除以256只保留其餘數。336除以256餘數是80,對應的ASCII值是P。
第四行,WORDS為65618使用int輸出原值,列印%hd轉換說明時printf()只使用最後2個位元組。這相當於65618除以65536的餘數,這裡餘數是82.%hd為82,82對應的ASCII碼為R,%C輸出為R

image

混淆整型和浮點型。

cat floatcnv.c 
#include <stdio.h>
int main(void)
{
     float n1 = 3.0;
    double n2 = 3.0;
    long n3 = 2000000000;
    long n4 = 1234567890;

    printf("%.1e %.1e %.1e %.1e\n", n1, n2, n3, n4);
    printf("%ld %ld\n", n3, n4);
    printf("%ld %ld %ld %ld\n", n1, n2, n3, n4);

    return 0;
}
[root@hans_tencent_centos82 ~]# ./floatcnv 
3.0e+00 3.0e+00 0.0e+00 3.2e-319
2000000000 1234567890
2000000000 1234567890 0 0

引數傳遞:
分析引數傳遞的原理(引數傳遞機制因實現而異):
    printf("%ld %ld %ld %ld\n", n1, n2, n3, n4);
該呼叫告訴計算機把變數n1、n2、n3、和n4的值傳遞給程式。程式把傳入的值放入被稱為棧的記憶體區域。計算機根據變數型別把這些值放入棧中。因此,n1被儲存在棧中,佔8位元組(float型別轉換double型別)。同樣n2也在棧中佔8位元組,而n3,n4在棧中分別佔4位元組。然後控制轉到pritnf()函式,該函式根據轉換說明從棧中讀取值。%ld轉換說明表明printf()應該讀取4位元組,所以printf()讀取棧中前4位元組作為第1個值。這就是n1的前半部分,將被解釋成一個long型別的整數。根據下一個%ld轉換說明,printf()再讀取4位元組,這是n1的後半部分,將被解釋成第2個long型別的整數。類似地,根據第3個和第4個%ld,printf()讀取n2的前半部分和後半部分,並解釋成兩個long型別的整數。因此,對於n3和n4,雖然用對了轉換說明,但printf()還是讀錯了位元組。

image

4.3.2 printf()的返回值

大部署C函式都有一個返回值,這是函式計算並返回給主調程式的值。可以把返回值像其他值一樣使用。printf()函式也有一個返回值,它返回列印字元的個數。如果有輸出錯誤,printf()則返回一個負值。printf()的返回值是其列印輸出功能的附帶用途,通常很少用到,但在檢查輸出錯誤時可能會用到。

[root@hans ~]# cat  printval.c                 
#include <stdio.h>

int main(void){
    int bph2o = 212;
    int rv;

    rv = printf("%d F is water's boiling point.\n", bph2o);
    printf("The printf() function printed %d  characters.\n", rv);

    return 0;

}
[root@hans ~]# ./printval 
212 F is water's boiling point.
The printf() function printed 32  characters.

首先,程式用rv = printf()的形式把printf()的返回值賦給rv.因此該語句執行了兩項任務:列印資訊和給變數賦值。其次,注意計算針對所有字元數,包括空格和不可見的換行符(\n)。
4.3.3 列印較長的字串

有時printf()語句太長,在螢幕上不方便閱讀。如果空白(空格、製表符、換行符)僅用於分隔不同的部分,C編譯器會忽略它們。一條語句可以寫成多行,只需在不同部分之間輸入空白即可。但不能在以引號括起來的字串早間斷行。

printf("The printf() function printed %d  characters.\n", 
		rv);   #沒有問題
printf("The printf() function printed %d  
		characters.\n", rv);  #錯誤

斷行的3種方法:

[root@hans ~]# cat longstrg.c 
#include <stdio.h>

int main(){
    printf("Here's one way to print a ");
    printf("long string.\n");   //1
    printf("Here's another way to print a \
long string.\n"); //2
    printf("Here's the newest way to print a "
            "long string.\n"); /*3, ANSI C */

    return 0;
}
[root@hans ~]# ./longstrg 
Here's one way to print a long string.
Here's another way to print a long string.
Here's the newest way to print a long string.
三種形式是等效的。

4.4 使用scanf()

scanf()可以讀取不同格式的資料,從鍵盤輸入的都是文字,因為鍵盤只能生成文字字元:字母、數字和標點符號。

scanf()把輸入的字串轉換成整數、浮點數、字元或字串,而printf()正好相反,它把整數、浮點數、字元和字串轉換成顯示在螢幕上的文字。

scanf()和printf()類似,也使用格式字串和引數列表。scanf()中的格式字串表明字元輸入流的目標資料型別。兩個函式主要的區別在引數列表中。printf()函式使用變數、常量和表示式,而scanf()函式使用指向變數的指標。在這裡只需記住以下兩條簡單的規則:

  • 如果用scanf()讀取基本變數型別的值,在變數名前加一個&
  • 如果用scanf()把字串讀入字元陣列中,不要使用&

示例:

[root@hans ~]# cat input.c 
#include <stdio.h>

int main(void){
    int age;  			//變數

    float assets;		//變數
    char pet[5];		//變數

    printf("Enter your age, assets, and favorite pet.\n");
    scanf("%d %f", &age, &assets); /* use & */
    scanf("%s", pet); /* char arrary not use &*/
    printf("%d $%.2f %s\n", age, assets, pet);

    return 0;
}
[root@hans ~]# ./input 
Enter your age, assets, and favorite pet.
38
92360.88 llama
38 $92360.88 llama

**ANSI C中scanf()的轉換說明 **

轉換說明 含義
%c 把輸入解釋成一個字元
%d 把輸入解釋成一個有符號十進位制整數
%e,%f,%g,%a 把輸入解釋成一個浮點數
%E,%F,%G,%A 把輸入解釋成一個浮點數
%i 把輸入解釋成一個有符號十進位制整數(C99標準新增了%a)
%o 把輸入解釋成一個有符號八進位制整數(C99標準新增了%A)
%p 把輸入解釋成一個指標(地址)
%s 把輸入解釋成字串。從第1個非空白字元開始,到下一個空白字元之前的所有字元都是輸入
%u 把輸入解釋成一個無符號十進位制整數
%x,%X 把輸入解釋成一個有符號十六進位制整數

scanf()的轉換說明中的修飾符

轉換說明 含義
* 抑制賦值, 示例:"%*d"
數字 最大欄位寬度。輸入達到最大欄位寬度處,或第1次遇到空白字元時停止 示例:"%10s"
hh 把整數作為 signed char 或 unsigned char 型別讀取 示例:"%hhd"、"%hhu"
ll 把整數作為 long long 或 unsigned long long 型別讀取(C99) 示例:"%lld"、"%llu"
h、l或L "%hd"和"%hi"表明把對應的值儲存為 short int 型別 "%ho"、"%hx"和"%hu"表明把對應的值儲存為 unsigned short int 型別 "%ld"和"%li"表示把對應的值儲存為 long 型別 "%lo"、"%lx"和"%lu"表明把對應的值儲存為 unsigned long 型別 "%le"、"%lf"和"%lg"表明把對應的值儲存為 double 型別 在e、f和g前面使用L而不是l,表明把對應的值儲存為 long double 型別。如果沒有修飾符,d、i、o和x表明對應的值被儲存為 int 型別,f和g表明把對應的值儲存為 float 型別
j 在整形轉換說明後面時,表明使用 intmax_t 或 uintmax_t 型別(C99) 示例:"%jd"、"%ju"
z 在整形轉換說明後面時,表明使用 sizeof 的返回型別(C99) 示例:"%zd"、"%zo"
t 在整形轉換說明後面時,表明使用表示兩個指標差值的型別(C99) 示例:"%td"、"%tx"
4.4.1 從scanf()角度看輸入

scanf()怎麼讀取輸入:

假設scanf()根據一個%d轉換說明讀取一個整數。scanf()函式每次讀取一個字元,跳過所有的空白字元,直至遇到第1個非空白字元才開始讀取。因為要讀取整數,所以scanf()希望發現一個數字字元或者一個符號(+或-)。如果找到一個數字或符號,它便儲存該字元,並讀取下一個字元。如果下一個字元是數字,它便儲存該數字並讀取下一個字元。scanf()不斷地讀取和儲存字元,直至遇到非數字字元。如果遇到一個非數字字元,它便認為讀到了整數的末尾。然後,scanf()把非數字字元放回輸入。這意味著程式在下一次讀取輸入時,首先讀到的是上一次讀取丟棄的非數字字元。
最後,scanf()y計算已讀取數字(可能還有符號)相應的數值,並將計算後的值放入指定的變數中。

如果使用欄位寬度,scanf()會在欄位結尾或第1個空白字元處停止讀取(兩個條件滿足一個便停止)。

如果第1個非空白字元是A而不是數字,scanf()將停在那裡,並把A放回輸入中,不會把值賦給指定變數。程式在下一次讀取輸入時,首先讀到的是字元是A。如果程式只使用%d轉換說明,scanf()就一直無法越過A讀下一個字元。另外,如果使用帶多個轉換說明的scanf(),C規定在第1個出錯處停止讀取輸入。

其他數值匹配的轉換說明讀取輸入和用%d的情況相同,區別在於scanf()會把更多字元識別成數字的一部分。
4.4.2 格式字串的普通字元

scanf()函式允許把普通字元放在格式字串中,除空格字元外的普通字元必須與輸入字串嚴格匹配。如:

scanf("%d,%d", &n,&m)
使用者輸入時必須這樣進行輸入兩個整數:
88,121
由於格式字串中,%d後面緊跟逗號,所以必須在輸入88後面再輸入一個逗號,但由於scanf()會跳過整數前面的空白,所以下面的輸入都可以:
88, 121

88,
121

除了%c其他轉換說明都會自動跳過待輸入值前面所有的空白。因此,scanf("%d%d",&n, &m)scanf("%d %d",&n, &m)的行為相同。對於%c在格式字串中新增一個空格字元會有所不同。scanf("%c", %ch)從輸入中的第1個字元開始讀取,而scanf(" %c", %ch)則從第1個非空白字元開始讀取。

4.4.3 scanf()的返回值

scanf()函式返回成功讀取的項數。如果沒有讀取任何項,且需要讀取一個數字而使用者卻輸入一個非數值字串,scanf()便返回0。當scanf()檢測到“檔案結尾”時,會返回EOF(通常#define指令把EOF定義為-1),

4.5 printf()和scanf()的*修飾符

printf()和scanf()都可以使用*修飾符來修改轉換說明的含義。

printf()中的*修飾符

如果你不想預先指定欄位寬度,希望通過程式指定,那麼可以用*修飾符代替欄位寬度。但還是要用一個引數告訴函式,欄位寬度應該是多少。也就是說,如果轉換說明是%d,那麼引數列表中應包含*d對應的值(也可用於浮點值指定精度和欄位寬度)。

[root@hans ~]# cat varwid.c 
#include <stdio.h>

int main(void){
    unsigned width, precision;
    int number = 256;
    double weight = 242.5;

    printf("Enter a field width:\n");
    scanf("%d", &width);
    printf("The number is :%*d\n", width, number);
    printf("Now enter a width and a precision:\n");
    scanf("%d %d", &width, &precision);
    printf("Weight = %*.*f\n", width, precision, weight);
    printf("Done!\n");

    return 0;
}
[root@hans ~]# ./varwid 
Enter a field width:
6
The number is :   256
Now enter a width and a precision:
8 3
Weight =  242.500
Done!

scanf()中*的用法

*放在%和轉換字元之間時,會使得scanf()跳過相應的輸出項。

[root@hans ~]# cat skip2.c 
#include <stdio.h>
int main(void){
   int n;

   printf("Please enter three integers:\n");
   scanf("%*d %*d %d", &n);
   printf("The last integer was %d\n", n);

    return 0;
}
[root@hans ~]# ./skip2 
Please enter three integers:
2013 2014 2015
The last integer was 2015

//在程式需要讀取檔案中特定列的內容時,這項跳過功能很有用。
4.5.1 printf()的用法提示

想把資料列印成列,指定固定欄位寬度很有用。因為預設的欄位寬度是待列印數字的寬度。

printf("%d %d %d\n", val1, val2, val3);列印3次出來的結果是:
12 234 1222
5 0 23
22334 2322 10001

printf("%9d %9d %9d\n", val1, val2, val3);列印3次出來的結果是:
       12       234      1222
        5         0        23
    22334      2322     10001

在文字中嵌入一個數字,通常指定一個小於或等於該數字寬度的欄位會比較方便。

float distance = 10.22
printf("Count Beppo ran %.2f miles in 3 hours.\n", distance);

Count Beppo ran 10.22 miles in 3 hours.

如果轉換說明改為%10.2f:
float distance = 10.22
printf("Count Beppo ran %10.2f miles in 3 hours.\n", distance);

Count Beppo ran      10.22 miles in 3 hours.

本地化設定

美國和世界上的許多地區都使用一個點來分隔十進位制值的整數部分和小數部分。printf()和scanf()都沒有提供逗號的轉換說明,C語言考慮了這種情況。C程式可以選擇特定的本地化設定。

C 標準有兩個本地化設定:"C"和“”(空字串)。預設情況下,程式使用"C"本地化設定,而“ ”本地化設定可以替換當前系統中使用的本地語言環境

5 關鍵概念

C語言用char型別表示單個字元,用字串表示字元序列。字元常量是一種字元形式,即用雙引號把字元括起來。可以把字串儲存在字元陣列中,字串無論是表示成字元常量還是儲存在字元陣列中都以一個叫做空字元的隱藏字元結尾。

在程式中,最好用#define定義數值常量,用const關鍵字宣告的變數為只讀變數。

C語言的標準輸入函式(scanf())和標準輸出函式(printf())都使用一種系統。在該系統中第1個引數中的轉換說明必須與後續引數中的值相匹配。

空白字元(製表符、空格和換行符)在scanf()處理輸入時起著至關秣的作用,除了%c模式(讀取下一個字元),scanf()在讀取輸入時會跳過非空白字元前的所有空白字元。然後一直讀取字元。直到遇到空白字元或與正在讀取字元不匹配的字元。

相關文章