天天看點

《C Primer Plus》 學習筆記第四章:字元串和格式化輸入/輸出

C Primer Plus

  • 第四章:字元串和格式化輸入/輸出
    • 4.1 前導程式
    • 4.2 字元串簡介
      • 4.2.1 char類型數組和null字元
      • 4.2.2 使用字元串
      • 4.2.3 strlen()函數
    • 4.3 常量和C預處理器
      • 4.3.1 const限定符
      • 4.3.2 明示常量
    • 4.4 printf()和scanf()
      • 4.4.1 printf()函數
      • 4.4.2 使用printf()函數
      • 4.4.3 printf()的轉換說明修飾符
      • 4.4.4 轉換說明的意義
      • 4.4.5 使用scanf()
      • 4.4.6 printf()和scanf()的*修飾符(因系統格式要求,下文用‘米’代替該符号)
      • 4.4.7 printf()的用法提示
    • 4.5 關鍵概念
    • 4.6 本章小結
    • 4.7 程式設計練習

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

前言:本章節将詳細介紹C語言的兩個輸入/輸出函數:scanf()和printf()。同時介紹函數strlen()以及關鍵字const的使用,最後簡要介紹一個重要工具——C預處理器指令,并學習如何定義、使用符号常量。

4.1 前導程式

首先我們先來看一個小程式4.1 talkback.c

#include <stdio.h>
#include <string.h>  //提供strlen()函數的原型
#define DENSITY 62.4  //人體密度(機關:磅/立方英尺)

int main(void)
{
    float weight,volume;
    int size,letters;
    char name[40];
    
    printf("Hi!What's your first name?\n");
    scanf("%s",name);  //name沒有&字首
    printf("%s,What's your weight in pounds?\n",name);
    scanf("%f",&weight);  //weight有&字首
    size = sizeof(name);  //擷取數組name占用的位元組記憶體
    letters = strlen(name);  //擷取數組name中存放的字元串實際占用的記憶體
    volume = weight / DENSITY;
    printf("Well,%s,your volume is %2.2f cubic feet.\n",name,volume);
    printf("Also,your first name has %d letters,\n",letters);
    printf("and we have %d bytes to store it.\n",size);
    
    return 0;
}
           

運作程式,通過printf()和scanf()函數進行人機互動——輸入YYC(Enter)和 130(Enter),輸出如下:

Hi!What's your first name?
YYC
YYC,What's your weight in pounds?
130
Well,YYC,your volume is 2.08 cubic feet.
Also,your first name has 3 letters,
and we have 40 bytes to store it.
           

在該程式中我們會發現一些新的特性:

  • 在這我們定義了一個字元數組name,用數組(array)存儲字元串,在該程式中,使用者名被存儲在數組中,該數組占用記憶體中40個連續的位元組,每個位元組存儲一個字元值;
  • 使用%s轉換說明來處理字元串的輸入和輸出。值得注意的是,在scanf()中,name沒有&字首,而weight有(這點在後面章節會詳細解釋);
  • 用C預處理器把字元常量DENSITY定義為62.4;
  • 用strlen()擷取字元串的長度。

4.2 字元串簡介

字元串(character string)是一個或多個字元的序列,如下所示:

“Zing went the strings of my herat!”

雙引号不屬于字元串的一部分,僅告知編譯器它括起來的是字元串,正如單引号用于辨別單個字元一樣。

4.2.1 char類型數組和null字元

在C語言中,沒有專門用于存儲字元串的變量類型,字元串都是被存儲在char類型的數組中。數組由連續的存儲單元組成,字元串中的字元都被存儲在相鄰的存儲單元中(見下圖4.1)。

《C Primer Plus》 學習筆記第四章:字元串和格式化輸入/輸出

注意上圖中數組末尾的字元\0,這是空字元,C語言用它标記字元串的結束。

此外,我們還需要知曉空字元并不是數字0,它是一個非列印字元,其ASCII碼值是0(可查閱ASCII表輔助了解)。C中的字元串一定是以空字元結束,這意味着數組的容量必須至少比待存儲字元串數多1.例如上述程式4.1中字元數組name有40個存儲單元,其實際最多隻能存儲39個字元,剩下一個位元組留給空字元!

4.2.2 使用字元串

老規矩,我們先來看個小程式4.2 praise1.c

#include <stdio.h>
#define PRAISE "You are an extraordinary being."

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

運作程式,輸入Angela Plains(Enter),輸出結果如下所示:

What's your name?
Angela Plains
Hello,Angela.You are an extraordinary being.
           

%s告訴printf()列印一個字元串。%s出現了兩次,是因為程式要列印兩個字元串:一個存儲在name數組中:一個由PRAISE來表示。在編寫程式時,讀者不用親自把空字元放入字元串末尾,scanf()在讀取輸入時就已經完成了這項工作。也不用在字元串常量PRAISE末尾添加空字元,編譯器會自動識别并且在末尾加上空字元。

《C Primer Plus》 學習筆記第四章:字元串和格式化輸入/輸出

細心的讀者會發現,在實際輸出中,我們輸入的是Angela Plains,但輸出的卻隻有前面的單詞Angela。這是因為scanf()在讀取輸入時,當其讀取到正确格式的字元後,此後再遇到==空白(空格、制表符、換行符)==時它就不再讀取輸入(未讀取正确格式字元前的空白不影響)。

根據%s轉換說明,scanf()隻會讀取字元串中的一個單詞,而不是一整句話。

注意:字元串和字元的差別:字元串常量“x”和字元‘x’是不同的,字元‘x’是基本類型(char),而“x”是派生類型(char 數組),此外字元串常量“x”實際上是由兩個字元組成----字元’x’和空字元\0。

《C Primer Plus》 學習筆記第四章:字元串和格式化輸入/輸出

4.2.3 strlen()函數

上一章我們有提到sizeof運算符,它是以位元組為機關給出對象的大小。strlen()函數給出字元串中的字元長度。我們來通過下面的這個程式,分析一下兩者的差別。

#include <stdio.h>
#include <string.h>  //提供strlen()函數的原型 
#define PRAISE "You are an extraordinary being."

int main(void)
{
	char name[40];
	
	printf("What's your name?\n");
	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;
}
           

運作程式,輸入Serendipity Chance(Enter),輸出結果為:

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個存儲單元,但是隻有前11個單元用來存儲字元串Serendipity,是以strlen()函數得出的結果是11,name數組的第12個存儲單元用來存放空字元\0,strlen()并未将其計入。下圖示範了這個概念:

《C Primer Plus》 學習筆記第四章:字元串和格式化輸入/輸出

對于PRAISE,用strlen()得出的也是字元串常量中的字元數(包括空格和标點符号)。而sizeof()則是将字元串末尾不可見的空字元也計算在内。

4.3 常量和C預處理器

有時,在程式中要使用常量。例如,可以這樣計算圓的周長:

circumference = 3.14159 * diameter;

這裡,常量3.14159代表pi(π)。在該例中,輸入實際值便可使用這個常量。然而,這種情況使用符号常量(symbolic constant)會更好。之是以這樣說,是因為使用符号常量不僅可以使其含義表達的更加清楚,同時當某個程式中多處使用同一個常量,有時我們需要改變它的值,如果此時程式使用了符号常量,那我們隻需要改變符号常量的定義便可一次修改完成,而不需要進入到代碼中逐一查找并修改。

那我們又該如何建立符号常量呢?方法一是聲明一個變量,然後将該變量設定為所需的常量。可以這樣寫:

float taxrate;

taxrate = 0.015;

這樣做提供了一個符号名,但是taxrate是一個變量,程式可能會無意間改變它的值。C語言還提供了一個更好的方案——C預處理器。第2 章中介紹了預處理器如何使用#include包含其他檔案的資訊。預處理器也可用來定義常量。隻需在程式頂部添加下面一行:

#define TAXRATE 0.015

編譯程式時,程式中所有的TAXRATE都會被替換成0.015。這一過程被稱為編譯時替換(compile-time substitution)。在運作程式時,程式中所有的替換均已完成。通常,這樣定義的常量也稱為明示常量(manifest constant)。

請注意格式,首先是#define,接着是符号常量名(TAXRATE),然後是符号常量的值(0.015)(注意,其中并沒有=符号)。是以,其通用格式如下:

#define NAME value

實際應用時,用標明的符号常量名和合适的值來替換NAME和value。注意,末尾不用加分号,因為這是一種由預處理器處理的替換機制。為什麼TAXRATE 要用大寫?用大寫表示符号常量是 C 語言一貫的傳統,大寫常量隻是為了提高程式的可讀性,即使全用小寫來表示符号常量,程式也能照常運作。

程式4.4 pizza.c

#include <stdio.h>
#define PI 3.14159

int main(void)
{
	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;
} 
           

輸入6.0(Enter),printf()語句中的%1.2f表明,結果被四舍五入為兩位小數輸出。輸出如下:

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!”

4.3.1 const限定符

C90标準新增了const關鍵字,用于限定一個變量為隻讀。其聲明如下:

const int MONTHS = 12; // MONTHS在程式中不可更改,值為12

這使得MONTHS成為一個隻讀值。也就是說,可以在計算中使用MONTHS,可以列印MONTHS,但是不能更改MONTHS的值。

4.3.2 明示常量

C頭檔案limits.h和float.h分别提供了與整數類型和浮點類型大小限制相關的詳細資訊。每個頭檔案都定義了一系列供實作使用的明示常量。例如,limits.h頭檔案包含以下類似的代碼:

#define INT_MAX +32767

#define INT_MIN -32768

這些明示常量代表int類型可表示的最大值和最小值。如果系統使用32位的int,該頭檔案會為這些明示常量提供不同的值。如果在程式中包含limits.h頭檔案,就可編寫下面的代碼:printf(“Maximum int value on this system = %d\n”, INT_MAX);

下面我們通過程式4.5 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;
}
           

輸出如下:

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-038
float precision = 6 digits
float epsilon = 1.192093e-007
           

4.4 printf()和scanf()

printf()函數和scanf()函數能讓使用者可以與程式交流,它們是輸入/輸出函數,或簡稱為I/O函數。雖然printf()是輸出函數,scanf()是輸入函數,但是它們的工作原理幾乎相同。兩個函數都使用格式字元串和參數清單。我們先介紹printf(),再介紹scanf()。

4.4.1 printf()函數

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

《C Primer Plus》 學習筆記第四章:字元串和格式化輸入/輸出

4.4.2 使用printf()函數

程式4.6中使用了一些轉換說明:

#include <stdio.h>
#define PI 3.141593
int main(void)
{
	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;
}

           

輸出結果為:

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,…);

待列印項1、待列印項2等都是要列印的項。它們可以是變量、常量,甚至是在列印之前先要計算的表達式。

例如,考慮下面的語句:

printf(“The %d contestants ate %f berry pies.\n”, number,pies);

格式字元串是雙引号括起來的内容。上面語句的格式字元串包含了兩個待列印項number和poes對應的兩個轉換說明。格式字元串實際包含了兩種形式不同的資訊:實際要列印的字元和轉換說明。

《C Primer Plus》 學習筆記第四章:字元串和格式化輸入/輸出

警告:格式字元串中的轉換說明一定要與後面的每個項相比對,若忘記這個基本要求會導緻嚴重的後果。由于 printf()函數使用%符号來辨別轉換說明,是以當我們需要列印%時,隻需要使用兩個%符号就行了。

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

在%和轉換字元之間插入修飾符可修飾基本的轉換說明。表4.4和表4.5列出可作為修飾符的合法字元。如果要插入多個字元,其書寫順序應該與表4.4中列出的順序相同。

《C Primer Plus》 學習筆記第四章:字元串和格式化輸入/輸出
《C Primer Plus》 學習筆記第四章:字元串和格式化輸入/輸出

4.7程式示範了字段寬度在列印整數時的效果

#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;
}
           

輸出為:

*959*
*959*
*       959*
*959       *
           

第2個轉換說明是%2d,其對應的輸出結果應該是 2 字段寬度。因為待列印的整數有 3 位數字,是以字段寬度自動擴大以符合整數的長度。最後一個轉換說明是%-10d,其對應的輸出結果是 10 個空格寬度,-标記說明列印的數字位于字段的左側。

4.8程式示範了浮點型格式的效果

#include <stdio.h>

int main(void)
{
	const double RENT = 3852.99;//const ???RENT???? 
	
	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;
}
           

該程式中使用了const關鍵字,限定變量為隻讀。輸出結果為:

*3852.990000*
*3.852990e+003*
*3852.99*
*3853.0*
*  3852.990*
*3.853E+003*
*+3852.99*
*0003852.99*
           

本例的第1個轉換說明是%f。在這種情況下,字段寬度和小數點後面的位數均為系統預設設定,即字段寬度是容納帶列印數字所需的位數和小數點後列印6位數字。第2個轉換說明是%e。預設情況下,編譯器在小數點的左側列印1個數字,在小數點的右側列印6個數字。這樣列印的數字太多!解決方案是指定小數點右側顯示的位數,程式中接下來的 4 個例子就是這樣做的。請注意,第4個和第6個例子對輸出結果進行了四舍五入。第7個轉換說明中包含了+标記,這使得列印的值前面多了一個代數符号(+)。0标記使得列印的值前面以0填充以滿足字段****要求。注意,轉換說明%010.2f的第1個0是标記,句點(.)之前、标記之後的數字(本例為10)是指定的字段寬度。

程式4.9 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;
}
           

輸出結果為:

1f 1F 0x1f
**42** 42**-42**
**    6**  006**00006**  006**
           

第1行輸出中,1f是十六進制數,等于十進制數31。第1行printf()語句中,根據%x列印出1f,%X列印出1F,%#x列印出0x1f。第 2 行輸出示範了如何在轉換說明中用空格在輸出的正值前面生成前導空格,負值前面不産生前導空格。第3行輸出示範了如何在整型格式中使用精度(%5.3d)生成足夠的前導0以滿足最小位數的要求(本例是3)。然而,使用0标記會使得編譯器用前導0填充滿整個字段寬度。最後,如果0标記和精度一起出現,0标記會被忽略。

程式4.10 stringf.c示範了字元串格式

#include <stdio.h>
#define BLURB "Auyhentic imitation!"

int main(void)
{
	printf("[%2s]\n",BLURB);
	printf("[%24s]\n",BLURB);
	printf("[%24.5s]\n"BLURB);
	printf("[%-24.5s]\n"BLURB);
	
	return 0;
}
           

輸出結果為:

[Auyhentic imitation!]
[    Auyhentic imitation!]
[                   Auyhe]
[Auyhe                   ]
           

注意,雖然第1個轉換說明是%2s,但是字段被擴大為可容納字元串中的所有字元。還需注意,精度限制了待列印字元的個數。[%24.5s]中.5告訴printf()隻列印5個字元。

4.4.4 轉換說明的意義

轉換說明把以二進制格式儲存在計算機中的值轉換成一系列字元/字元串以便于顯示。例如,數字76在計算機内部的存儲格式是二進制數01001100。%d轉換說明将其轉換成字元7和6,并顯示為76;%x轉換說明把相同的值(01001100)轉換成十六進制記數法4c;%c轉換說明把01001100轉換成字元L。

轉換(conversion) 可能會誤導讀者認為原始值被替換成轉換後的值。實際上,轉換說明是翻譯說明,%d的意思是“把給定的值翻譯成十進制整數文本并列印出來”。

(1)轉換不比對

轉換說明應該與待列印值的類型相比對。通常都有多種選擇。例如,如果要列印一個int類型的值,可以使用%d、%x或%o。這些轉換說明都可用于列印int類型的值,其差別在于它們分别表示一個值的形式不同。類似地,列印double類型的值時,可使用%f、%e或%g。

程式4.11 示範了一些不比對的整型轉換示例

#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;
}
           

輸出結果為:

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
           

第1行,num變量對應的轉換說明%hd和%hu輸出的結果都是336。這沒有任何問題。然而,第2行mnum變量對應的轉換說明%u(無符号)輸出的結果卻為65200,并非期望的336。這是由于有符号short int類型的值在我們的參考系統中的表示方式所緻——首先,short int的大小是2位元組;其次,系統使用二進制補碼來表示有符号整數。這種方法,數字0~32767(215 )代表它們本身,而數字32768~65535(216 )則表示負數。其中,65535表示-1,65534表示-2,以此類推。是以,-336表示為65200(即, 65536-336)。第3行示範了如果把一個大于255的值轉換成字元會發生什麼情況。在我們的系統中,short int是2位元組,char是1位元組。當printf()使用%c列印336時,它隻會檢視儲存336的2位元組中的後1位元組。這種截斷(見圖4.8)相當于用一個整數除以256,隻保留其餘數。在這種情況下,餘數是80,對應的ASCII值是字元P。用專業術語來說,該數字被解釋成“以256為模”(modulo 256),即該數字除以256後取其餘數。

《C Primer Plus》 學習筆記第四章:字元串和格式化輸入/輸出

最後,我們在該系統中列印比short int類型最大整數(32767)更大的整數(65618)。這次,計算機也進行了求模運算。在本系統中,應把數字65618儲存為4位元組的int類型值。用%hd轉換說明列印時, printf()隻使用最後2個位元組。

程式4.12 示範不比對的浮點型轉換效果

#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;
}
           

輸出結果為:

3.0e+00 3.0e+00 3.1e+46 1.7e+266
2000000000 1234567890
0 1074266112 0 1074266112
           
  • 第1行也說明了前面提到的内容:float類型的值作為printf()參數時會被轉換成double類型。在本系統中,float是4位元組,但是為了printf()能正确地顯示該值,n1被擴成8位元組。
  • 第2行輸出顯示,隻要使用正确的轉換說明,printf()就可以列印n3和n4。
  • 第3行輸出顯示,如果printf()語句有其他不比對的地方,即使用對了轉換說明也會生成虛假的結果。用 %ld轉換說明列印浮點數會失敗,但是在這裡,用%ld列印long類型的數竟然也失敗了!問題出在C如何把資訊傳遞給函數。具體情況因編譯器實作而異。

程式的具體結果,需要讀者自己去仔細分析,在此就不過多講解。我們下面主要來講解一下”參數傳遞“:

參數傳遞機制因實作而異。下面以我們的系統為例,分析參數傳遞的原理。函數調用如下:

printf(“%ld %ld %ld %ld\n”, n1, n2, n3, n4);

該調用告訴計算機把變量n1、n2、n3和n4的值傳遞給程式。這是一種常見的參數傳遞方式。程式把傳入的值放入被稱為棧(stack)的記憶體區域。計算機根據變量類型(不是根據轉換說明) 把這些值放入棧中。是以,n1被儲存在棧中,占8位元組(float類型被轉換成double類型)。同樣,n2也在棧中占8位元組,而n3和n4在棧中分别占4位元組。然後,控制轉到printf()函數。該函數根據轉換說明(不是根據變量類型)從棧中讀取值。%ld轉換說明表明printf()應該讀取4位元組,是以printf()讀取棧中的前4位元組作為第1個值。這是n1的前半部分,将被解釋成一個long類型的整數。根據下一個%ld轉換說明,printf()再讀取4位元組,這是n1的後半部分,将被解釋成第2個long類型的整數(見圖4.9)。類似地,根據第3個和第4個%ld,printf()讀取n2的前半部分和後半部分,并解釋成兩個long類型的整數。是以,對于n3和n4,雖然用對了轉換說明,但printf()還是讀錯了位元組。

《C Primer Plus》 學習筆記第四章:字元串和格式化輸入/輸出

(2)printf()的傳回值

第2章提到過,大部分C函數都有一個傳回值,這是函數計算并傳回給主調程式(calling program)的值。例如,C庫包含一個sqrt()函數,接受一個數作為參數,并傳回該數的平方根。可以把傳回值賦給變量,也可以用于計算,還可以作為參數傳遞。總之,可以把傳回值像其他值一樣使用。printf()函數也有一個傳回值,它傳回列印字元的個數。如果有輸出錯誤,printf()則傳回一個負值(printf()的舊版本會傳回不同的值)。

程式4.13 printf()的傳回值

#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;
}
           

輸出結果為:

212 F is water's boiling point.
The printf() function printed 32 characters.
           

首先,程式用rv = printf(…);的形式把printf()的傳回值賦給rv。是以,該語句執行了兩項任務:列印資訊和給變量指派。其次,注意計算針對所有字元數,包括空格和不可見的換行符(\n)。

(3)列印較長的字元串

有時,printf()語句太長,在螢幕上不友善閱讀。如果空白(空格、制表符、換行符)僅用于分隔不同的部分,C 編譯器會忽略它們。在這,我們不能在雙引号括起來的字元串中間斷行。如果這樣寫:

printf(“The printf() function printed %d

characters.\n”, rv);

C編譯器會報錯:字元串常量中有非法字元。在字元串中,可以使用\n來表示換行字元,但是不能通過按下Enter(或Return)鍵産生實際的換行符。

正确的列印有三種方法:

方法1:使用多個printf()語句。因為第1個字元串沒有以\n字元結束,是以第2個字元串緊跟第1個字元串末尾輸出。

方法2:用反斜杠(\)和Enter(或Return)鍵組合來斷行。這使得光标移至下一行,而且字元串中不會包含換行符。其效果是在下一行繼續輸出。但是,下一行代碼必須和程式清單中的代碼一樣從最左邊開始。如果縮進該行,比如縮進5個空格,那麼這5個空格就會成為字元串的一部分。

方法3:ANSI C引入的字元串連接配接。在兩個用雙引号括起來的字元串之間用空白隔開,C編譯器會把多

個字元串看作是一個字元串。

4.4.5 使用scanf()

剛學完輸出,接下來我們轉至輸入——學習scanf()函數。C庫包含了多個輸入函數,scanf()是最通用的一個,因為它可以讀取不同格式的資料。當然,從鍵盤輸入的都是文本,因為鍵盤隻能生成文本字元:字母、數字和标點符号。如果要輸入整數 2014,就要鍵入字元 2、0、1、4。如果要将其儲存為數值而不是字元串,程式就必須把字元依次轉換成數值,這就是scanf()要做的。scanf()把輸入的字元串轉換成整數、浮點數、字元或字元串,而printf()正好與它相反,把整數、浮點數、字元和字元串轉換成顯示在螢幕上的文本。

scanf()和 printf()類似,也使用格式字元串和參數清單。scanf()中的格式字元串表明字元輸入流的目标資料類型。兩個函數主要的差別在參數清單中。printf()函數使用變量、常量和表達式,而scanf()函數使用指向變量的指針。這裡,讀者不必了解如何使用指針,隻需記住以下兩條簡單的規則:

  • 如果用scanf()讀取基本變量類型的值,在變量名前加上一個&;
  • 如果用scanf()把字元串讀入字元數組中,不要使用&。

程式4.15中的小程式示範了這兩條規則

#include <stdio.h>
int main(void)
{
	int age;      // 變量
	float assets;   // 變量
	char pet[30];   // 字元數組,用于儲存字元串
	
	printf("Enter your age, assets, and favorite pet.\n");
	scanf("%d %f", &age, &assets); // 這裡要使用&
	scanf("%s", pet);        // 字元數組不使用&
	printf("%d $%.2f %s\n", age, assets, pet);
	
	return 0;
}
           

運作程式,輸入38(Enter),92360.88(Enter), llama(Enter),輸入結果為:

Enter your age, assets, and favorite pet.
38
92360.88
llama
38 $92360.88 llama
           

scanf()函數使用空白(換行符、制表符和空格)把輸入分成多個字段。在依次把轉換說明和字段比對時跳過空白。注意,在輸入時隻要在每個輸入項之間輸入至少一個換行符、空格或制表符即可,可以在一行或多行輸入,唯一例外的是%c轉換說明。根據%c,scanf()會讀取每個字元,包括空白。我們稍後詳述這部分。

(1)scanf()函數的轉換說明

scanf()函數所用的轉換說明與printf()函數幾乎相同。主要的差別是,對于float類型和double類型,printf()都使用%f、%e、%E、%g和%G轉換說明。而scanf()隻把它們用于float類型,對于double類型時要使用l修飾符。

《C Primer Plus》 學習筆記第四章:字元串和格式化輸入/輸出

(2)scanf()函數的轉換說明修飾符

可以在表4.6所列的轉換說明中(百分号和轉換字元之間)使用修飾符。如果要使用多個修飾符,必須按表4.7所列的順序書寫。

《C Primer Plus》 學習筆記第四章:字元串和格式化輸入/輸出
《C Primer Plus》 學習筆記第四章:字元串和格式化輸入/輸出

如你所見,使用轉換說明比較複雜,而且這些表中還省略了一些特性。省略的主要特性是,從高度格式化源中讀取標明資料,如穿孔卡或其他資料記錄。

(3)從scanf()角度看輸入

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

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

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

如果使用%s 轉換說明,scanf()會讀取除空白以外的所有字元。scanf()跳過空白開始讀取第 1 個非空白字元,并儲存非空白字元直到再次遇到空白。這意味着 scanf()根據%s 轉換說明讀取一個單詞,即不包含空白字元的字元串。如果使用字段寬度,scanf()在字段末尾或第1個空白字元處停止讀取。無法利用字段寬度讓隻有一個%s的scanf()讀取多個單詞。

實際上,在C語言中scanf()并不是最常用的輸入函數。這裡重點介紹它是因為它能讀取不同類型的資料。C 語言還有其他的輸入函數,如 getchar()和 fgets()。這兩個函數更适合處理一些特殊情況,如讀取單個字元或包含空格的字元串。

(4)格式字元串中的普通字元

scanf()函數允許把普通字元放在格式字元串中。除空格字元外的普通字元必須與輸入字元串嚴格比對。例如,假設在兩個轉換說明中添加一個逗号:scanf(“%d,%d”, &n, &m);

scanf()函數将其解釋成:使用者将輸入一個數字、一個逗号,然後再輸入一個數字。也就是說,使用者必須像下面這樣進行輸入兩個整數:

88,121 (,字元後面有空白符也不影響)

除了%c,其他轉換說明都會自動跳過待輸入值前面所有的空白。是以,scanf(“%d%d”, &n, &m)與scanf(“%d %d”, &n, &m)的行為相同。

(5)scanf()的傳回值

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

4.4.6 printf()和scanf()的*修飾符(因系統格式要求,下文用‘米’代替該符号)

printf()和scanf()都可以使用米修飾符來修改轉換說明的含義。但是,它們的用法不太一樣。首先,我們來看printf()的*修飾符。

如果你不想預先指定字段寬度,希望通過程式來指定,那麼可以用米修飾符代替字段寬度。但還是要用一個參數告訴函數,字段寬度應該是多少。也就是說,如果轉換說明是%*d,那麼參數清單中應包含米和 d對應的值。這個技巧也可用于浮點值指定精度和字段寬度。

程式4.16 示範了相關用法

#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;
}
           

運作程式,輸入6(Enter),8(Space),3(Enter),輸出結果為:

Enter a field width:
6
The number is : 256:
Now enter a width and a precision:
8 3
Weight = 242.500
Done!
           

變量width提供字段寬度,number是待列印的數字。因為轉換說明中米在d的前面,是以在printf()的參數清單中,width在number的前面。同樣,width和precision提供列印weight的格式化資訊。

這裡,使用者首先輸入6,是以6是程式使用的字段寬度。類似地,接下來使用者輸入8和3,說明字段寬度是8,小數點後面顯示3位數字。一般而言,程式應根據weight的值來決定這些變量的值。

scanf()中*的用法與此不同。把米放在%和轉換字元之間時,會使得scanf()跳過相應的輸出項。

程式清單4.17就是一個例子

#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;
}
           

運作程式,輸入2013(Space),2014(Space),2015(Enter),輸出結果為:

Please enter three integers:
2013 2014 2015
The last integer was 2015
           

在程式需要讀取檔案中特定列的内容時,這項跳過功能很有用。

4.4.7 printf()的用法提示

想把資料列印成列,指定固定字段寬度很有用。因為預設的字段寬度是待列印數字的寬度,如果同一列中列印的數字位數不同,列印出來的數字可能參差不齊。這個時候指定相同的字寬和對齊方式就可以使其看起來更加整齊。例如:printf(“%9d %9d %9d\n”, val1, val2, val3);

4.5 關鍵概念

  1. C語言用char類型表示單個字元,用字元串表示字元序列。字元常量是一種字元串形式,即用雙引号把字元括起來:“Good luck,my friend”。可以把字元串儲存在字元數組(由記憶體中相鄰的位元組組成)中。字元串,無論是表示成字元常量還是儲存在字元數組中,都以一個叫做(空字元\0)的隐藏字元結尾。
  2. 在程式中,最好用#define 定義數值常量,用 const 關鍵字聲明的變量為隻讀變量。在程式中使用符号常量(明示常量),提高了程式的可讀性和可維護性。
  3. C 語言的标準輸入函數(scanf())和标準輸出函數(printf())都使用一種系統。在該系統中,第1個參數 中的轉換說明必須與後續參數中的值相比對。
  4. 空白字元(制表符、空格和換行符)在 scanf()處理輸入時起着至關重要的作用。除了%c 模式(讀取下一個字元),scanf()在讀取輸入時會跳過非空白字元前的所有空白字元,然後一直讀取字元,直至遇到空白字元或與正在讀取字元不比對的字元。

4.6 本章小結

  • 字元串是一系列被視為一個處理單元的字元。在C語言中,字元串是以空字元(ASCII碼是0)結尾的一系列字元。可以把字元串儲存在字元數組中。數組是一系列同類型的項或元素。要確定有足夠多的元素來儲存整個字元串(包括空字元)。
  • strlen()函數(聲明在string.h頭檔案中)可用于獲得字元串的長度(末尾的空字元不計算在内)。scanf()函數中的轉換說明是%s時,可讀取一個單詞。
  • C預處理器為預處理器指令(以#符号開始)查找源代碼程式,并在開始編譯程式之前處理它們。處理器根據#include指令把另一個檔案中的内容添加到該指令所在的位置。#define指令可以建立明示常量(符号常量),即代表常量的符号。limits.h和float.h頭檔案用#define定義了一組表示整型和浮點型不同屬性的符号常量。另外,還可以使用const限定符建立定義後就不能修改的變量。
  • printf()和scanf()函數對輸入和輸出提供多種支援。兩個函數都使用格式字元串,其中包含的轉換說明表明待讀取或待列印資料項的數量和類型。另外,可以使用轉換說明控制輸出的外觀:字段寬度、小數位和字段内的布局。

4.7 程式設計練習

1.編寫一個程式,提示使用者輸入名和姓,然後以“名,姓”的格式列印出來。

#include <stdio.h>
int main(void)
{
	char first_name[40];
	char last_name[40];
	
	printf("Please enter your first and last name:\n");
	scanf("%s %s",first_name,last_name);
	printf("Hello,%s,%s\n",first_name,last_name);
	
	return 0;
}
           

運作程式,輸入Yongchao Yan(Enter),輸出結果為:

Please enter your first and last name:
Yongchao Yan
Hello,Yongchao,Yan
           

2.編寫一個程式,提示使用者輸入名和姓,并執行一下操作:

a.列印名和姓,包括雙引号;

b.在寬度為20的字段右端列印名和姓,包括雙引号;

c.在寬度為20的字段左端列印名和姓,包括雙引号;

d.在比姓名寬度寬3的字段中列印名和姓。

#include <stdio.h>
#include <string.h>
int main(void)
{
	char name[40];
	unsigned int num;
	
	printf("Please enter your  name:\n");
	scanf("%s",name);
	num = strlen(name);
	printf("Your name is:\"%s\"\n",name);
	printf("Your name is:\"%20s\"\n",name);
	printf("Your name is:\"%-20s\"\n",name);
	printf("Your name is:%*s\n",num+3,name);
	
	return 0;
}
           

運作程式,輸入YanYongchao(Enter),輸出結果為:

Please enter your  name:
YanYongchao
Your name is:"YanYongchao"
Your name is:"         YanYongchao"
Your name is:"YanYongchao         "
Your name is:   YanYongchao
           

3.編寫一個程式,讀取一個浮點數,首先以小數點記數法列印,然後以指數記數法列印。用下面的格式進行輸出(系統不同,指數記數法顯示的位數可能不同):

a.The input is 21.3 or 2.1e+001.

b.The input is +21.290 or 2.129E+001.

#include <stdio.h>
int main(void)
{
	float num;
	
	printf("Please enter a floating point number:\n");
	scanf("%f",&num);
	printf("The input is %.1f or %.1e.\n",num,num);
	printf("The input is %+.3f or %.3E.\n",num,num);
	
	return 0;
}
           

運作程式,輸入21.2901(Enter),輸出結果為:

Please enter a floating point number:
21.2901
The input is 21.3 or 2.1e+001.
The input is +21.290 or 2.129E+001.
           

4.編寫一個程式,提示使用者輸入身高(機關:英寸)和姓名,然後以下面的格式顯示使用者剛輸入的資訊:

Dabney, you are 6.208 feet tall

使用float類型,并用/作為除号。如果你願意,可以要求使用者以厘米為機關輸入身高,并以米為機關顯示出來。

#include <stdio.h>
int main(void)
{
	char name[40];
	float height_m,height_cm;
	
	printf("Please enter your height(cm):");
	scanf("%f",&height_cm);
	height_m = height_cm / 100;
	printf("Please enter your name:");
	scanf("%s",name);
	printf("%s,you are %.3fm tall.\n",name,height_m);
	
	return 0;
}
           

運作程式,輸入171(Enter),YanYongchao(Enter),輸出結果為:

Please enter your height(cm):171
Please enter your name:YanYongchao
YanYongchao,you are 1.710m tall.
           

5.編寫一個程式,提示使用者輸入以兆位每秒(Mb/s)為機關的下載下傳速度和以兆位元組(MB)為機關的文

件大小。程式中應計算檔案的下載下傳時間。注意,這裡1位元組等于8位。使用float類型,并用/作為除号。該

程式要以下面的格式列印 3 個變量的值(下載下傳速度、檔案大小和下載下傳時間),顯示小數點後面兩位數字:

At 18.12 megabits per second, a file of 2.20 megabytes

downloads in 0.97 seconds.

#include<stdio.h>
int main(void)
{
	float speed,file_size,dw_time;
	
	printf("Please Enter the speed of download(Mb/s):");
	scanf("%f",&speed);
	printf("Please Enter the size of file(MB):");
	scanf("%f",&file_size);
	dw_time=file_size*8/speed;
	printf("At %.2f megabits per second,a file of %.2f megabytes\n",speed,file_size);
	printf("downloads in %.2f seconds.\n",dw_time);
	
	return 0;
} 
           

運作程式,輸入18.120(Enter),2.201(Enter),輸出結果為:

Please Enter the speed of download(Mb/s):18.120
Please Enter the size of file(MB):2.201
At 18.12 megabits per second,a file of 2.20 megabytes
downloads in 0.97 seconds.
           

6.編寫一個程式,先提示使用者輸入名,然後提示使用者輸入姓。在一行列印使用者輸入的名和姓,下一行分

别列印名和姓的字母數。字母數要與相應名和姓的結尾對齊,如下所示:

Melissa Honeybee

7    8

接下來,再列印相同的資訊,但是字母個數與相應名和姓的開頭對齊,如下所示:

Melissa Honeybee

7    8

#include <stdio.h>
#include <string.h>
int main(void)
{
	unsigned int num_fir,num_last;
	char first_name[40],last_name[40];
	
	printf("Please enter your first name:\n");
	scanf("%s",first_name);
	num_fir = strlen(first_name);
	printf("Please enter your last name:\n");
	scanf("%s",last_name);
	num_last = strlen(last_name);
	printf("%s %s\n",first_name,last_name);
	printf("%*d %*d\n",num_fir,num_fir,num_last,num_last);
	printf("%s %s\n",first_name,last_name);	
	printf("%-*d %-*d\n",num_fir,num_fir,num_last,num_last);
	
	return 0;
}
           

運作程式,輸入Melissa(Enter),Honeybee(Enter),輸出結果為:

Please enter your first name:
Melissa
Please enter your last name:
Honeybee
Melissa Honeybee
      7        8
Melissa Honeybee
7       8
           

7.編寫一個程式,将一個double類型的變量設定為1.0/3.0,一個float類型的變量設定為1.0/3.0。分别顯示兩次計算的結果各3次:一次顯示小數點後面6位數字;一次顯示小數點後面12位數字;一次顯示小數點後面16位數字。程式中要包含float.h頭檔案,并顯示FLT_DIG和DBL_DIG的值。1.0/3.0的值與這些值一緻嗎?

#include <stdio.h>
#include <float.h>
int main(void)
{
	double dou_out= 1.0 / 3.0;
 	float flo_out = 1.0 / 3.0;
 	
 	printf(" float values: ");
 	printf("%.6f %.12f %.16f\n",flo_out,flo_out,flo_out);
 	printf("double values: ");
 	printf("%.6f %.12f %.16f\n",dou_out,dou_out,dou_out);
 	printf("FLT_DIG: %d\n", FLT_DIG);
 	printf("DBL_DIG: %d\n", DBL_DIG);
 	
 	return 0;
}
           

運作程式,輸出結果為:

float values: 0.333333 0.333333343267 0.3333333432674408
double values: 0.333333 0.333333333333 0.3333333333333333
FLT_DIG: 6
DBL_DIG: 15
           

8.編寫一個程式,提示使用者輸入旅行的裡程和消耗的汽油量。然後計算并顯示消耗每加侖汽油行駛的英

裡數,顯示小數點後面一位數字。接下來,使用1加侖大約3.785升,1英裡大約為1.609千米,把機關是英裡/加侖的值轉換為升/100公裡(歐洲通用的燃料消耗表示法),并顯示結果,顯示小數點後面 1 位數字。注意,美國采用的方案測量消耗機關燃料的行程(值越大越好),而歐洲則采用機關距離消耗的燃料測量方案(值越低越好)。使用#define 建立符号常量或使用 const 限定符建立變量來表示兩個轉換系數。

#include <stdio.h>
#define gallon_L 3.785
#define miles_KM 1.609

int main(void)
{
	float miles,gallo,get_value,get_rvalue;
	
	printf("Please enter the mileage of the trip(miles):");
	scanf("%f",&miles);
	printf("Please enter the amount of gas consumed for the trip(gallon):");
	scanf("%f",&gallo);
	get_value = miles / gallo;
	printf("The number of miles you travel per gallon is %.1f.\n",get_value);
	get_rvalue = (gallo*gallon_L*100)/(miles*miles_KM);
	printf("The amount of petrol you need to travel 100 kilometres is %.1f.\n",get_rvalue);
	
	return 0;
}
           

運作程式,輸入10(Enter),2(Enter),輸出結果為:

Please enter the mileage of the trip(miles):10
Please enter the amount of gas consumed for the trip(gallon):2
The number of miles you travel per gallon is 5.0.
The amount of petrol you need to travel 100 kilometres is 47.0.
           

繼續閱讀