作者 謝恩銘,公眾號「程序員聯(lián)盟」(微信號:coderhub)。
轉(zhuǎn)載請注明出處。
原文:http://www.lxweimin.com/p/2be7006765ec
《C語言探索之旅》全系列
內(nèi)容簡介
- 前言
- 字符類型
- 顯示字符
- 字符串其實就是字符的數(shù)組
- 字符串的創(chuàng)建和初始化
- 從 scanf 函數(shù)取得一個字符串
- 操縱字符串的一些常用函數(shù)
- 總結(jié)
- 第二部分第五課預告
1. 前言
上一課 C語言探索之旅 | 第二部分第三課:數(shù)組 ,我們結(jié)束了關(guān)于數(shù)組的旅程。
好了,這課我不說“廢話”,直接進入主題(但又好像不是我的風格...)。這一課我們還是會涉及一些指針和數(shù)組的知識。
字符串,這是一個編程的術(shù)語,用來描述“一段文字”。
一個字符串,就是我們可以在內(nèi)存中以變量的形式儲存的“一段文字”。
比如,用戶名是一個字符串,“程序員聯(lián)盟”是一個字符串。
但是我們之前的課說過,呆萌的電腦兄只認得數(shù)字,“眾里尋他千百度,電腦卻只認得數(shù)。”
所以實際上,電腦是不認得字母的,但是“古靈精怪”的計算機先驅(qū)們是如何使電腦可以“識別”字母呢?
接下來我們會看到,他們還是很聰明的。
2. 字符類型
在這個小部分,我們把注意力先集中在字符類型上。
如果你還記得,之前的課程中我們說過:char(有符號字符類型)是用來儲存范圍從 -128 到 127 的數(shù)的;unsigned char(無符號字符類型)用來儲存范圍從 0 到 255 的數(shù)。
注意: 雖然 char 類型可以用來儲存數(shù)值,但是在 C語言中卻鮮少用 char 來儲存一個數(shù)。
通常,即使我們要表示的數(shù)比較小,我們也會用 int 類型來儲存。
當然了,用 int 來儲存比用 char 來儲存在內(nèi)存上更占空間。但是今天的電腦基本上是不缺那點內(nèi)存的,“有內(nèi)存任性嘛”。
char 類型一般用來儲存一個字符,注意,是 一個 字符。
前面的課程也提到了,因為電腦只認得數(shù)字,所以計算機先驅(qū)們建立了一個表格(比較常見的有 ASCII 表, 更完整一些的有 Unicode 表),用來約定字符和數(shù)字之間的轉(zhuǎn)換關(guān)系,例如大寫字母 A 對應的數(shù)字是 65。
C語言可以很容易地轉(zhuǎn)換字符和其對應的數(shù)值。為了獲取到某個字符對應的數(shù)值(電腦底層其實都是數(shù)值),只需要把該字符用單引號括起來,像這樣:
'A'
在編譯的時候,'A' 會被替換成實際的數(shù)值 65。
我們來測試一下:
#include <stdio.h>
int main(int argc, char *argv[])
{
char letter = 'A';
printf("%d\n", letter);
return 0;
}
程序輸出:
65
所以,我們可以確信大寫字母 A 的對應數(shù)值是 65。類似地,大寫字母 B 對應 66, C 對應 67, 以此類推。
如果我們測試小寫字母,那你會看到 a 和 A 的數(shù)值是不一樣的,小寫字母 a 的數(shù)值是 97。
實際上,在大寫字母和小寫字母之間有一個很簡單的轉(zhuǎn)換公式,就是
小寫字母的數(shù)值 = 大寫字母的數(shù)值 + 32
所以電腦是區(qū)分大小寫的,看似呆萌的電腦兄還是可以的么。
大部分所謂“基礎”的字符都被編碼成 0 到 127 之間的數(shù)值了。在 ASCII 表(發(fā)音 [aski])的官網(wǎng) http://www.asciitable.com 上,我們可以看到大部分常用的字符的對應數(shù)值。
當然這個表我們也可以在其他網(wǎng)站上找到,比如維基百科,百度百科,等等。
3. 顯示字符
要顯示一個字符,最常用的還是 printf 函數(shù)啦。這個函數(shù)真的很強大,我們會經(jīng)常用到。
上面的例子中,我們用 %d 格式,所以顯示的是字符對應的數(shù)值(%d 是整型)。如果要顯示字符實際的樣子,需要用到 %c 格式(c 是英語 character 的首字母,表示“字符”):
int main(int argc, char *argv[])
{
char letter = 'A';
printf("%c\n", letter);
return 0;
}
程序輸出:
A
當然我們也可以用常見的 scanf 函數(shù)來請求用戶輸入一個字符,而后用 printf 函數(shù)打印:
int main(int argc, char *argv[])
{
char letter = 0;
scanf("%c", &letter);
printf("%c\n", letter);
return 0;
}
如果我輸入 C,那我將看到:
C
C
第一個字母 C 是我輸入給 scanf 函數(shù)的,第二個 C 是 printf 函數(shù)打印的。
以上就是對于字符類型 char 我們大致需要知道的,請牢記以下幾點:
- signed char(有符號字符類型)用來儲存范圍從 -128 到 127 的數(shù)。
- unsigned char(無符號字符類型)用來儲存范圍從 0 到 255 的數(shù)。
- C語言中,如果你沒寫 signed 或 unsigned 關(guān)鍵字,默認情況下是 signed(有符號)。
- 計算機先驅(qū)們給電腦規(guī)定了一個表,電腦可以遵照里面的轉(zhuǎn)換原則來轉(zhuǎn)換字符和數(shù)值,一般這個表是 ASCII 表。
- char 類型只能儲存一個字符。
- 'A' 在編譯時會被替換成實際的數(shù)值:65 。因此,我們使用單引號來獲得一個字符的值。
4. 字符串其實就是字符的數(shù)組
這一部分的內(nèi)容,就如這個小標題所言。
事實上:一個字符串就是一個“字符的數(shù)組”,僅此而已。
到這里,你是否對字符串有了更直觀的理解呢?
如果我們創(chuàng)建一個字符數(shù)組:
char string[5];
然后我們在數(shù)組的第一個成員上儲存 'H',就是 string[0] = 'H',第二個成員上儲存 'E'(string[1] = 'H'),第三個成員上儲存 'L'(string[2] = 'L'),第四個成員儲存 'L'(string[3] = 'L'),第五個成員儲存 'O'(string[4] = 'O'),那么我們就構(gòu)造了一個字符串。
下圖對于字符串在內(nèi)存中是怎么存儲的,可以給出一個比較直觀的印象(注意: 實際的情況比這個圖演示的要略微復雜一些,待會兒會解釋):
上圖中,我們可以看到一個數(shù)組,擁有 5 個成員,在內(nèi)存上連續(xù)存放,構(gòu)成一個字符串 "HELLO"(表示“喂,你好”)。
對于每一個儲存在內(nèi)存地址上的字符,我們用了單引號把它括起來,是為了突出實際上儲存的是數(shù)值,而不是字符。在內(nèi)存上,儲存的就是此字符對應的數(shù)值。
實際上,一個字符串可不是就這樣結(jié)束了,上面的圖示其實不完整。
一個字符串必須在最后包含一個特殊的字符,稱為“字符串結(jié)束符”,它是 '\0',對應的數(shù)值是 0。
“為什么要在字符串結(jié)尾加這么一個多余的字符呢?”
問得好!
那是為了讓電腦知道一個字符串到哪里結(jié)束。
'\0' 用于告訴電腦:“停止,字符串到此結(jié)束了,不要再讀取了,先退下吧”。
因此,為了在內(nèi)存中存儲字符串 "HELLO"(5 個字符),用 5 個成員的字符數(shù)組是不夠的,需要 6 個!
因此每次創(chuàng)建字符串時,需要記得在字符數(shù)組的結(jié)尾留一個字符給 '\0'。
忘記字符串結(jié)束符是 C語言中一個常見的錯誤。
因此,下面才是正確展示我們的字符串 "HELLO" 在內(nèi)存中實際存放情況的示意圖:
如上圖所見,這個字符串包含 6 個字符,而不是 5 個。
也多虧了這個字符串結(jié)束符 '\0',我們就無需記得字符串的長度了,因為它會告訴電腦字符串在哪里結(jié)束。
因此,我們就可以將我們的字符數(shù)組作為參數(shù)傳遞給函數(shù),而不需要傳遞字符數(shù)組的大小了。
這個好處只針對字符數(shù)組,你可以在傳遞給函數(shù)時將其寫為 char * 或者 char[] 類型。
對于其他類型的數(shù)組,我們總是要在某處記錄下它的長度。
5. 字符串的創(chuàng)建和初始化
如果我們想要用 "Hello" 來初始化字符數(shù)組 string,我們可以用以下的方式來實現(xiàn)。當然,有點沒效率:
char string[6]; // 六個 char 構(gòu)成的數(shù)組,為了儲存:H-e-l-l-o + \0
string[0] = 'H';
string[1] = 'e';
string[2] = 'l';
string[3] = 'l';
string[4] = 'o';
string[5] = '\0';
雖然是笨辦法,但至少行得通。
我們用 printf 函數(shù)來測試一下。
要使 printf 函數(shù)能顯示字符串,我們需要用到 %s 這個符號(s 就是英語 string 的首字母,表示“字符串”):
#include <stdio.h>
int main(int argc, char *argv[])
{
char string[6]; // 六個 char 構(gòu)成的數(shù)組,為了儲存:H-e-l-l-o + \0
string[0] = 'H';
string[1] = 'e';
string[2] = 'l';
string[3] = 'l';
string[4] = 'o';
string[5] = '\0';
// 顯示字符串內(nèi)容
printf("%s\n", string);
return 0;
}
程序輸出:
Hello
如果我們的字符串內(nèi)容多起來,上面的方法就更顯拙劣了。其實啊,初始化字符串還有更簡單的一種方式(讀者:“你好‘奸詐’,不早講,害我寫代碼這么辛苦...”):
int main(int argc, char *argv[])
{
char string[] = "Hello"; // 字符數(shù)組的長度會被自動計算
printf("%s\n", string);
return 0;
}
以上程序的第一行,我們寫了一個char [] 類型的變量,其實也可以寫成 char * ,同樣是可以運行的:
char *string = "Hello";
這種方法就比之前一個字符一個字符初始化的方法高大上多了,因為只需要在雙引號里輸入你想要創(chuàng)建的字符串,C語言的編譯器就很智能地為你計算好字符串的大小。
編譯器計算你輸入的字符的數(shù)目,然后再加上一個 '\0' 的長度(是 1),就把你的字符串里的字符一個接一個寫到內(nèi)存某個地方,在最后加上 '\0' 這個字符串結(jié)束符,就像我們剛才用第一種方式自己一步步做的。
但是簡便也有缺陷。我們會發(fā)現(xiàn),對于字符數(shù)組來說,這種方法只能用于初始化,你在之后的程序中就不能再用這種方式來給整個數(shù)組賦值了,比如你不能這樣:
char string[] = "Hello";
string = "nihao"; // --> 出錯!
只能一個字符一個字符地改,例如:
string[0] = 'j'; // --> 可以!
但是問題又來了,對于用 char * 來聲明的字符串,我們可以在之后整個重新賦值,但是不可以單獨修改某個字符:
char *string = "Hello";
string = "nihao"; // --> 可以!
這樣是可以的。但是如果修改其中的一個字符,就不可以:
string[1] = 'a'; // --> 出錯!
很有意思吧。大家可以親自動手試試。所以這里就引出了一個話題:
指針和數(shù)組根本就是兩碼事!
為什么會出現(xiàn)上述的情況呢?(請注意:下面的這塊內(nèi)容比較難,如果看不懂,也可以暫時跳過。不過建議測試一下給出的代碼)。
那是因為:
char stringArray[] = "Hello";
這樣聲明的是一個字符數(shù)組,里面的字符串是儲存在內(nèi)存的變量區(qū),是在棧上,所以可以修改每個字符的內(nèi)容,但是不可以通過數(shù)組名整體修改:
stringArray = "nihao"; // --> 出錯!
只能一個個單獨改:
stringArray[0] = 'a'; // --> 可以!
因為之前的課程里說過,stringArray 這個數(shù)組的名字表示的是數(shù)組首元素的首地址。
char *stringPointer = "Hello";
這樣聲明的是一個指針,stringPointer 是指針的名字。指針變量在 32 位系統(tǒng)下,永遠占 4 個 byte(字節(jié));在 64 位系統(tǒng)下,永遠占 8 個 byte(字節(jié))。其值為某一個內(nèi)存的地址。
所以 stringPointer 里面只是存放了一個地址,這個地址上存放的字符串是常量字符串。這個常量字符串存放在內(nèi)存的靜態(tài)區(qū),不可以更改。
和上面的字符數(shù)組情況不一樣,上面的字符數(shù)組是本身存放了那一整個字符串。
stringPointer[0] = 'a'; // --> 出錯!
但是可以改變 stringPointer 指針的指向:
stringPointer = "nihao"; // --> 可以?。ㄒ驗榭梢孕薷闹羔樦赶蚰睦铮?
大家可以自己測試一下:
char *n1 = "it";
char *n2 = "it";
printf("%p\n%p\n", n1, n2); //用 %p 查看地址
會發(fā)現(xiàn)二者的結(jié)果是一樣的,指向同一個地址!
再進一步測試(生命在于折騰...):
char *n1 = "it";
char *n2 = "it";
printf("%p\n%p\n", n1, n2);
n1 = "haha";
printf("%p\n%p\n", n1, n2);
你會發(fā)現(xiàn)以上程序,指針 n2 所指向的地址一直沒變,而 n1 在經(jīng)過
n1 = "haha";
之后,它所指向的地址就改變了。
經(jīng)過上面地分析,可能很多朋友還是有點暈,特別是可能不太清楚內(nèi)存各個區(qū)域的區(qū)別。
如果有興趣深入探究,既可以自己去看相關(guān)的 C語言書籍。也可以參考下表和一些解釋,如果暫時不想把自己搞得更暈,可以跳過,以后講到相關(guān)內(nèi)容時自然更好理解。
名稱 | 內(nèi)容 |
---|---|
代碼段 | 可執(zhí)行代碼、字符串常量 |
數(shù)據(jù)段 | 已初始化全局變量、已初始化全局靜態(tài)變量、局部靜態(tài)變量、常量數(shù)據(jù) |
BSS 段 | 未初始化全局變量,未初始化全局靜態(tài)變量 |
棧 | 局部變量、函數(shù)參數(shù) |
堆 | 動態(tài)內(nèi)存分配 |
一般情況下,一個可執(zhí)行二進制程序(更確切的說,在 Linux 操作系統(tǒng)下為一個進程單元)在存儲(沒有調(diào)入到內(nèi)存運行)時擁有 3 個部分,分別是代碼段、數(shù)據(jù)段和 BSS 段。
這 3 個部分一起組成了該可執(zhí)行程序的文件。
(1) 代碼段(code segment / text segment):存放 CPU 執(zhí)行的機器指令。通常代碼段是可共享的,這使得需要頻繁被執(zhí)行的程序只需要在內(nèi)存中擁有一份拷貝即可。代碼段也通常是只讀的,這樣可以防止其他程序意外地修改其指令。另外,代碼段還規(guī)劃了局部數(shù)據(jù)所申請的內(nèi)存空間信息。
代碼段通常是指用來存放程序執(zhí)行代碼的一塊內(nèi)存區(qū)域。這部分區(qū)域的大小在程序運行前就已經(jīng)確定,并且內(nèi)存區(qū)域通常屬于只讀,某些架構(gòu)也允許代碼段為可寫,即允許修改程序。在代碼段中,也有可能包含一些只讀的常數(shù)變量,例如字符串常量等。
(2) 數(shù)據(jù)段(data segment):或稱全局初始化數(shù)據(jù)段/靜態(tài)數(shù)據(jù)段(initialized data segment / data segment)。該段包含了在程序中明確被初始化的全局變量、靜態(tài)變量(包括全局靜態(tài)變量和局部靜態(tài)變量)和常量數(shù)據(jù)。
(3) 未初始化數(shù)據(jù)段:也稱 BSS(Block Started by Symbol)。該段存入的是全局未初始化變量、靜態(tài)未初始化變量。
而當程序被加載到內(nèi)存單元時,則需要另外兩個域:棧和堆。
(4) 棧(stack):存放函數(shù)的參數(shù)值、局部變量的值,以及在進行任務切換時存放當前任務的上下文內(nèi)容。
(5) 堆(heap):用于動態(tài)內(nèi)存分配(之后的課程馬上會講到),就是使用 malloc / free 系列函數(shù)來管理的內(nèi)存空間。
在將應用程序加載到內(nèi)存空間執(zhí)行時,操作系統(tǒng)負責代碼段、數(shù)據(jù)段和 BSS 段的加載,并將在內(nèi)存中為這些段分配空間。
棧也由操作系統(tǒng)分配和管理,而不需要程序員顯式地管理;堆由程序員自己管理,即顯式地申請和釋放空間。
很多 C語言的初學者搞不懂指針和數(shù)組到底有什么樣的關(guān)系。
現(xiàn)在就告訴大家:指針和數(shù)組之間沒有任何關(guān)系!它們是“清白”的...
指針就是指針:指針變量在 32 位系統(tǒng)下,永遠占 4 個 byte(字節(jié));在 64 位系統(tǒng)下,永遠占 8 個 byte(字節(jié))。其值為某一個內(nèi)存的地址。指針可以指向任何地方,但不是任何地方你都能通過這個指針變量訪問到。
數(shù)組就是數(shù)組:其大小與元素的類型和個數(shù)有關(guān)。定義數(shù)組時必須指定其元素的類型和個數(shù)。數(shù)組可以存任何類型的數(shù)據(jù),但不能存函數(shù)。
推薦大家去看《C語言深度解剖》這本只有 100 多頁的 PDF,是國人寫的,里面對于指針和數(shù)組分析得很全面。
不禁感嘆,C語言果然是博(xiang)大(dang)精(ke)深(pa)。
6. 從 scanf 函數(shù)取得一個字符串
我們可以用 scanf 函數(shù)獲取用戶輸入的一個字符串,也要用到 %s 符號。
但是有一個問題:就是你不能知道用戶究竟會輸入多少字符。
假如我們的程序是問用戶他的名字是什么。那么他可能回答 Tom,只有三個字符,或者 Bruce LI,就有 8 個字符了。
所以我們只能用一個足夠大的數(shù)組來存儲名字,例如 char[100]。你會說這樣太浪費內(nèi)存了,但是前面我們也說過了,目前的電腦一般不在乎這點內(nèi)存。
所以我們的程序會是這樣:
int main(int argc, char *argv[])
{
char name[100];
printf("請問您叫什么名字 ? ");
scanf("%s", name);
printf("您好, %s, 很高興認識您!\n", name);
return 0;
}
運行程序:
請問您叫什么名字?Oscar
您好,Oscar,很高興認識您!
7. 操縱字符串的一些常用函數(shù)
字符串在 C語言里是很常用的。事實上,此刻你在電腦或手機屏幕上看到的這些單詞、句子等,都是在電腦內(nèi)存里的字符數(shù)組。
為了方便我們操縱字符串,C語言的設計者們在 string 這個標準庫中已經(jīng)寫好了很多函數(shù),可供我們使用。
當然在這以前,需要在你的 .c 源文件中引入這個頭文件:
#include <string.h>
下面我們就來介紹它們之中最常用的一些吧:
strlen:計算字符串的長度
strlen 函數(shù)返回一個字符串的長度(不包括 '\0')。
為什么名字是 strlen?其實很好記:
- str 是 string(表示“字符串”)的前三個字母。
- len 是 length(表示“長度”)的前三個字母。
因此,strlen 就是“字符串長度”。
函數(shù)原型是這樣:
size_t strlen(const char* string);
注意:size_t 是一個特殊的類型,它意味著函數(shù)返回一個對應大小的數(shù)目。
不是像 int,char,long,double 之類的基本類型,而是一個被“創(chuàng)造”出來的類型。
在接下來的課程中我們就會學到如何創(chuàng)建自己的變量類型。
暫時說來,我們先滿足于將 strlen 函數(shù)的返回值存到一個 int 變量里(電腦會把 size_t 自動轉(zhuǎn)換成 int)。當然,嚴格來說應該用 size_t 類型,但是我們這里暫時不深究了。
函數(shù)的參數(shù)是 const char * 類型,之前的課程中我們學過,const(只讀的變量)表明此類型的變量是不能被改變的,所以函數(shù) strlen 并不會改變它的參數(shù)的值。
寫個程序測試一下 strlen 函數(shù):
#include <string.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
char string[] = "Hello";
int stringLength = 0;
// 將字符串的長度儲存到 stringLength 中
stringLength = strlen(string);
printf("字符串 %s 中有 %d 個字符\n", string, stringLength);
return 0;
}
程序運行,顯示:
字符串 Hello 中有 5 個字符
當然了,這個 strlen 函數(shù),其實我們自己也可以很容易地實現(xiàn)。只需要用一個循環(huán),從開始一直讀入字符串中的字符,計算數(shù)目,一直讀到 '\0' 字符結(jié)束循環(huán)。
我們就來實現(xiàn)我們自己的 strlen 函數(shù)好了:
#include <string.h>
#include <stdio.h>
int stringLength(const char *string);
int main(int argc, char *argv[])
{
char string[] = "Hello";
int length = 0;
length = stringLength(string);
printf("字符串 %s 中有 %d 個字符\n", string, length);
return 0;
}
int stringLength(const char *string)
{
int charNumber = 0;
char currentChar = 0;
do
{
currentChar = string[charNumber];
charNumber++;
} while (currentChar != '\0'); // 我們做循環(huán),直到遇到 '\0',跳出循環(huán)
charNumber--; // 我們將 charNumber 減一,使其不包含 '\0' 的長度
return charNumber;
}
程序輸出:
字符串 Hello 中有 5 個字符
strcpy:把一個字符串的內(nèi)容復制到另一個字符串里
為什么名字是 strcpy?其實很好記:
- str 是 string 的前三個字母。
- cpy 是 copy(表示“拷貝,復制”)的縮寫。
因此,strcpy 就是“字符串拷貝”。
函數(shù)原型:
char* strcpy(char* targetString, const char* stringToCopy);
這個函數(shù)有兩個參數(shù):
targetString:這是一個指向字符數(shù)組的指針,我們要復制字符串到這個字符數(shù)組里。
stringToCopy:這是一個指向要被復制的字符串的指針。
函數(shù)返回一個指向 targetString 的指針,通常我們不需要獲取這個返回值。
用以下程序測試此函數(shù):
#include <string.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
/* 我們創(chuàng)建了一個字符數(shù)組 string,里面包含了幾個字符。
我們又創(chuàng)建了另一個字符數(shù)組 copy,包含 100 個字符,為了足夠容納拷貝過來的字符 */
char string[] = "Hello", copy[100] = {0};
strcpy(copy, string); // 我們把 string 復制到 copy 中
// 如果一切順利,copy 的值應該和 string 是一樣的
printf("string 是 %s\n", string);
printf("copy 是 %s\n", copy);
return 0;
}
程序輸出:
string 是 Hello
copy 是 Hello
如果我們的 copy 數(shù)組的長度小于 6,那么程序會出錯,因為 string 的總長度是 6(最后有一個 '\0' 字符串結(jié)束符)。
strcpy 的原理圖解如下:
strcat:連接兩個字符串
為什么名字是 strcat?其實很好記:
- str 是 string 的前三個字母。
- cat 是 concatenate (表示“連結(jié),使連鎖”)的縮寫。
因此,strcat 就是“字符串連結(jié)”。
strcat 函數(shù)的作用是連接兩個字符串,就是把一個字符串接到另一個的結(jié)尾。
函數(shù)原型:
char* strcat(char* string1, const char* string2);
因為 string2 是 const 類型,所以我們就想到了,這個函數(shù)肯定是將 string2 的內(nèi)容接到 string1 的結(jié)尾,改變了 string1 所指向的字符指針,然后返回指向 string1 所指字符數(shù)組的指針。
略微有點拗口,但不難理解吧。
寫個程序測試一下:
#include <string.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
/* 我們創(chuàng)建了兩個字符串,字符數(shù)組 string1 需要足夠長,因為我們要將 string2 的內(nèi)容接到其后 */
char string1[100] = "Hello ", string2[] = "Oscar!";
strcat(string1, string2); // 將 string2 接到 string1 后面
// 如果一切順利,那么 string1 的值應該會變?yōu)?"Hello Oscar!"
printf("string1 是 %s\n", string1);
// string2 沒有變
printf("string2 始終是 %s\n", string2);
return 0;
}
程序輸出:
string1 是 Hello Oscar!
string2 始終是 Oscar!
strcat 的原理如下:
當 strcat 函數(shù)將 string2 連接到 string1 的尾部時,它需要先刪去 string1 字符串最后的 '\0'。
strcmp:比較兩個字符串
為什么名字是 strcmp?其實很好記:
- str 是string 的前三個字母。
- cmp 是 compare(英語“比較”)的縮寫。
因此,strcmp 就是“字符串比較”。
函數(shù)原型:
int strcmp(const char* string1, const char* string2);
可以看到,strcmp 函數(shù)不能改變參數(shù) string1 和 string2,因為它們都是 const 類型。
這次,函數(shù)的返回值有用了。strcmp 返回:
- 0:當兩個字符串相等時。
- 非零的整數(shù)(負數(shù)或正數(shù)):當兩個字符串不等時。
用以下程序測試 strcmp 函數(shù):
#include <string.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
char string1[] = "Text of test", string2[] = "Text of test";
if (strcmp(string1, string2) == 0) // 如果兩個字符串相等
{
printf("兩個字符串相等\n");
}
else
{
printf("兩個字符串不相等\n");
}
return 0;
}
程序輸出:
兩個字符串相等
sprintf:向一個字符串寫入
當然,這個函數(shù)其實不是在 string.h 這個頭文件里,而是在 stdio.h 頭文件里。但是它也與字符串的操作有關(guān),所以我們也介紹一下,而且這個函數(shù)是很常用的。
看到 sprintf 函數(shù)的名字,大家是否想到了printf 函數(shù)呢?
printf 函數(shù)是向標準輸出(一般是屏幕)寫入東西,而 sprintf 是向一個字符串寫入東西。最前面的 s 就是英語 string 的首字母。
寫個程序測試一下此函數(shù):
#include <stdio.h>
int main(int argc, char *argv[])
{
char string[100];
int age = 18;
// 我們向 string 里寫入"你18歲了"
sprintf(string, "你 %d 歲了", age);
printf("%s\n", string);
return 0;
}
程序輸出:
你 18 歲了
其他常用的還有一些函數(shù),如 strstr(在字符串中查找一個子串),strchr(在字符串里查找一個字符),等等,我們就不一一介紹了。
8. 總結(jié)
電腦不認識字符,它只認識數(shù)字(0 和 1)。為了解決這個問題,計算機先驅(qū)們用一個表格規(guī)定了字符與數(shù)值的對應關(guān)系,最常用的是 ASCII 表和 Unicode 表。
字符類型 char 用來存儲一個字符,且只能存儲一個字符。實際上存儲的是一個數(shù)值,但是電腦會在顯示時將其轉(zhuǎn)換成對應的字符。
為了創(chuàng)建一個詞或一句話,我們需要構(gòu)建一個字符串,我們用字符數(shù)組來實現(xiàn)。
所有的字符串都是以 '\0' 結(jié)尾,這個特殊的字符 '\0' 標志著字符串的結(jié)束。
在 string 這個 C語言標準庫中,有很多操縱字符串的函數(shù),只需要引入頭文件 string.h 即可。
9. 第二部分第五課預告
今天的課就到這里,一起加油咯。
我是 謝恩銘,公眾號「程序員聯(lián)盟」(微信號:coderhub)運營者,慕課網(wǎng)精英講師 Oscar 老師,終生學習者。
熱愛生活,喜歡游泳,略懂烹飪。
人生格言:「向著標桿直跑」