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

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

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()在讀取輸入時會跳過非空白字符前的所有空白字符。然後一直讀取字符。直到遇到空白字符或與正在讀取字符不匹配的字符。

Tags: