字符串(character string)是一個或多個字符的序列,如下所示:
"Zing went the strings of my heart!"
雙引號不是字符串的一部分。雙引號僅告知編譯器它括起來的是字符串,正如單引號用於標識單個字符一樣。
一、char 類型數組和 null 字符
C 語言沒有專門用於存儲字符串的變量類型,字符串都被存儲在 char 類型的數組中。數組由連續的存儲單元組成,字符串中的字符被存儲在相鄰的存儲單元中,每個單元存儲一個字符(見圖 1)。
圖 1 數組中的字符串
注意圖 1 中數組末尾位置的字符 \0。這是空字符(null character),C 語言用它標記字符串的結束。空字符不是數字 0,它是非打印字符,其 ASCII 碼值是(或等價於)0。C 中的字符串一定以空字符結束,這意味着數組的容量必須至少比待存儲字符串中的字符數多 1。因此,程序清單 1 中有 40 個存儲單元的字符串,只能存儲 39 個字符,剩下一個字節留給空字符。
程序清單 1 talkback.c 程序
// talkback.c -- 演示與用戶交互
#include <stdio.h>
#include <string.h> // 提供strlen()函數的原型
#define DENSITY 62.4 // 人體密度(單位:磅/立方英尺)
int main()
{
float weight, volume;
int size, letters;
char name[40]; // 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 letters,\n",
letters);
printf("and we have %d bytes to store it.\n", size);
return 0;
}
運行 talkback.c 程序,輸入結果如下:
Hi! What's your first name?
Christine
Christine, what's your weight in pounds?
154
Well, Christine, your volume is 2.47 cubic feet.
Also, your first name has 9 letters,
and we have 40 bytes to store it.
那么,什么是數組?可以把數組看作是一行連續的多個存儲單元。用更正式的說法是,數組是同類型數據元素的有序序列。程序清單 1 通過以下聲明創建了一個包含 40 個存儲單元(或元素)的數組,每個單元存儲一個 char 類型的值:
char name[40];
name 后面的方括號表明這是一個數組,方括號中的 40 表明該數組中的元素數量。char 表明每個元素的類型(見圖 2)。
圖 2 聲明一個變量和聲明一個數組
字符串看上去比較復雜!必須先創建一個數組,把字符串中的字符逐個放入數組,還要記得在末尾加上一個\0。還好,計算機可以自己處理這些細節。
二、使用字符串
試着運行程序清單 2,使用字符串其實很簡單。
程序清單 2 praise1.c 程序
/* 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;
}
%s 告訴 printf() 打印一個字符串。%s 出現了兩次,因為程序要打印兩個字符串:一個存儲在 name 數組中;一個由 PRAISE 來表示。運行 praise1.c,其輸出如下所示:
What's your name? Angela Plains
Hello, Angela. You are an extraordinary being.
你不用親自把空字符放入字符串末尾,scanf() 在讀取輸入時就已完成這項工作。也不用在字符串常量 PRAISE 末尾添加空字符。稍后我們會解釋 #define 指令,現在先理解 PRAISE 后面用雙引號括起來的文本是一個字符串。編譯器會在末尾加上空字符。
注意(這很重要),scanf() 只讀取了 Angela Plains 中的 Angela,它在遇到第 1 個空白(空格、制表符或換行符)時就不再讀取輸入。因此,scanf() 在讀到 Angela 和 Plains 之間的空格時就停止了。一般而言,根據 %s 轉換說明,scanf() 只會讀取字符串中的一個單詞,而不是一整句。C 語言還有其他的輸入函數(如,fgets()),用於讀取一般字符串。
字符串和字符
字符串常量 "x" 和字符常量 'x' 不同。區別之一在於 'x' 是基本類型(char),而 "x" 是派生類型(char 數組);區別之二是 "x" 實際上由兩個字符組成:'x' 和空字符 \0(見圖 3)。
圖 3 字符 ‘x’ 和字符串 “x”
三、strlen() 函數
strlen() 函數給出字符串中的字符長度。因為 1 字節存儲一個字符,讀者可能認為把兩種方法應用於字符串得到的結果相同,但事實並非如此。請根據程序清單 3,在程序清單 2 中添加幾行代碼,看看為什么會這樣。
程序清單 3 praise2.c 程序
/* praise2.c */
// 如果編譯器不識別%zd,嘗試換成%u或%lu。
#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? ");
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;
}
如果使用 ANSI C 之前的編譯器,必須移除這一行:
#include <string.h>
string.h 頭文件包含多個與字符串相關的函數原型,包括 strlen()。
一般而言,C 把函數庫中相關的函數歸為一類,並為每類函數提供一個頭文件。例如,printf() 和 scanf() 都隸屬標准輸入和輸出函數,使用 stdio.h 頭文件。string.h 頭文件中包含了 strlen() 函數和其他一些與字符串相關的函數(如拷貝字符串的函數和字符串查找函數)。
注意,程序清單 3 使用了兩種方法處理很長的 printf() 語句。第 1 種方法是將 printf() 語句分為兩行(可以在參數之間斷為兩行,但是不要在雙引號中的字符串中間斷開);第 2 種方法是使用兩個 printf() 語句打印一行內容,只在第 2 條 printf() 語句中使用換行符(\n)。運行該程序,其交互輸出如下:
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 個單元存儲空字符,strlen() 並未將其計入。圖 4 演示了這個概念。
圖 4 strlen() 函數知道在何處停止
對於 PRAISE,用 strlen() 得出的也是字符串中的字符數(包括空格和標點符號)。然而,sizeof 運算符給出的數更大,因為它把字符串末尾不可見的空字符也計算在內。該程序並未明確告訴計算機要給字符串預留多少空間,所以它必須計算雙引號內的字符數。
C99 和 C11 標准專門為 sizeof 運算符的返回類型添加了 %zd 轉換說明,這對於 strlen() 同樣適用。對於早期的 C,還要知道 sizeof 和 strlen() 返回的實際類型(通常是 unsigned 或 unsigned long)。
另外,還要注意一點:sizeof 可以使用圓括號。何時使用圓括號取決於運算對象是類型還是特定量。運算對象是類型時,圓括號必不可少,但是對於特定量,圓括號可有可無。也就是說,對於類型,應寫成 sizeof(char) 或 sizeof(float);對於特定量,可寫成 sizeof name 或 sizeof 6.28。盡管如此,還是建議所有情況下都使用圓括號,如 sizeof(6.28)。
程序清單 3 中使用 strlen() 和 sizeof,完全是為了滿足讀者的好奇心。在實際應用中,strlen() 和 sizeof 是非常重要的編程工具。例如,在各種要處理字符串的程序中,strlen() 很有用。
原文:C 語言字符串簡介
(完)
