c语言中 字符串常量的界定符,C字符串操作函数
1.7.?分割字符串
很多文件格式或協(xié)議格式中會(huì)規(guī)定一些分隔符或者叫界定符(Delimiter),例如/etc/passwd文件中保存著系統(tǒng)的帳號(hào)信息:$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
...
每條記錄占一行,也就是說(shuō)記錄之間的分隔符是換行符,每條記錄又由若干個(gè)字段組成,這些字段包括用戶(hù)名、密碼、用戶(hù)id、組id、個(gè)人信息、主目錄、登錄Shell,字段之間的分隔符是:號(hào)。解析這樣的字符串需要根據(jù)分隔符把字符串分割成幾段,C標(biāo)準(zhǔn)庫(kù)提供的strtok函數(shù)可以很方便地完成分割字符串的操作。tok是Token的縮寫(xiě),分割出來(lái)的每一段字符串稱(chēng)為一個(gè)Token。#include char *strtok(char *str, const char *delim);
char *strtok_r(char *str, const char *delim, char **saveptr);
返回值:返回指向下一個(gè)Token的指針,如果沒(méi)有下一個(gè)Token了就返回NULL
參數(shù)str是待分割的字符串,delim是分隔符,可以指定一個(gè)或多個(gè)分隔符,strtok遇到其中任何一個(gè)分隔符就會(huì)分割字符串。看下面的例子。
例?25.2.?strtok
#include #include int main(void)
{
char str[] = "root:x::0:root:/root:/bin/bash:";
char *token;
token = strtok(str, ":");
printf("%s\n", token);
while ( (token = strtok(NULL, ":")) != NULL)
printf("%s\n", token);
return 0;
}$ ./a.out
root
x
0
root
/root
/bin/bash
結(jié)合這個(gè)例子,strtok的行為可以這樣理解:冒號(hào)是分隔符,把"root:x::0:root:/root:/bin/bash:"這個(gè)字符串分隔成"root"、"x"、""、"0"、"root"、"/root"、"/bin/bash"、""等幾個(gè)Token,但空字符串的Token被忽略。第一次調(diào)用要把字符串首地址傳給strtok的第一個(gè)參數(shù),以后每次調(diào)用第一個(gè)參數(shù)只要傳NULL就可以了,strtok函數(shù)自己會(huì)記住上次處理到字符串的什么位置(顯然這是通過(guò)strtok函數(shù)中的一個(gè)靜態(tài)指針變量記住的)。
用gdb跟蹤這個(gè)程序,會(huì)發(fā)現(xiàn)str字符串被strtok不斷修改,每次調(diào)用strtok把str中的一個(gè)分隔符改成'\0',分割出一個(gè)小字符串,并返回這個(gè)小字符串的首地址。(gdb) start
Breakpoint 1 at 0x8048415: file main.c, line 5.
Starting program: /home/akaedu/a.out
main () at main.c:5
5{
(gdb) n
6char str[] = "root:x::0:root:/root:/bin/bash:";
(gdb)
9token = strtok(str, ":");
(gdb) display str
1: str = "root:x::0:root:/root:/bin/bash:"
(gdb) n
10printf("%s\n", token);
1: str = "root\000x::0:root:/root:/bin/bash:"
(gdb)
root
11while ( (token = strtok(NULL, ":")) != NULL)
1: str = "root\000x::0:root:/root:/bin/bash:"
(gdb)
12printf("%s\n", token);
1: str = "root\000x\000:0:root:/root:/bin/bash:"
(gdb)
x
11while ( (token = strtok(NULL, ":")) != NULL)
1: str = "root\000x\000:0:root:/root:/bin/bash:"
剛才提到在strtok函數(shù)中應(yīng)該有一個(gè)靜態(tài)指針變量記住上次處理到字符串中的什么位置,所以不需要每次調(diào)用時(shí)都把字符串中的當(dāng)前處理位置傳給strtok,但是在函數(shù)中使用靜態(tài)變量是不好的,以后會(huì)講到這樣的函數(shù)是不可重入的。strtok_r函數(shù)則不存在這個(gè)問(wèn)題,它的內(nèi)部沒(méi)有靜態(tài)變量,調(diào)用者需要自己分配一個(gè)指針變量來(lái)維護(hù)字符串中的當(dāng)前處理位置,每次調(diào)用時(shí)把這個(gè)指針變量的地址傳給strtok_r的第三個(gè)參數(shù),告訴strtok_r從哪里開(kāi)始處理,strtok_r返回時(shí)再把新的處理位置寫(xiě)回到這個(gè)指針變量中(這是一個(gè)Value-result參數(shù))。strtok_r末尾的r就表示可重入(Reentrant),這個(gè)函數(shù)不屬于C標(biāo)準(zhǔn)庫(kù),是在POSIX標(biāo)準(zhǔn)中定義的。關(guān)于strtok_r的用法Man Page上有一個(gè)很好的例子:
例?25.3.?strtok_r
#include #include #include int main(int argc, char *argv[])
{
char *str1, *str2, *token, *subtoken;
char *saveptr1, *saveptr2;
int j;
if (argc != 4) {
fprintf(stderr, "Usage: %s string delim subdelim\n",
argv[0]);
exit(EXIT_FAILURE);
}
for (j = 1, str1 = argv[1]; ; j++, str1 = NULL) {
token = strtok_r(str1, argv[2], &saveptr1);
if (token == NULL)
break;
printf("%d: %s\n", j, token);
for (str2 = token; ; str2 = NULL) {
subtoken = strtok_r(str2, argv[3], &saveptr2);
if (subtoken == NULL)
break;
printf(" --> %s\n", subtoken);
}
}
exit(EXIT_SUCCESS);
}$ ./a.out 'a/bbb///cc;xxx:yyy:' ':;' '/'
1: a/bbb///cc
--> a
--> bbb
--> cc
2: xxx
--> xxx
3: yyy
--> yyy
a/bbb///cc;xxx:yyy:這個(gè)字符串有兩級(jí)分隔符,一級(jí)分隔符是:號(hào)或;號(hào),把這個(gè)字符串分割成a/bbb///cc、xxx、yyy三個(gè)子串,二級(jí)分隔符是/,只有第一個(gè)子串中有二級(jí)分隔符,它被進(jìn)一步分割成a、bbb、cc三個(gè)子串。由于strtok_r不使用靜態(tài)變量,而是要求調(diào)用者自己保存字符串的當(dāng)前處理位置,所以這個(gè)例子可以在按一級(jí)分隔符分割整個(gè)字符串的過(guò)程中穿插著用二級(jí)分隔符分割其中的每個(gè)子串。建議讀者用gdb的display命令跟蹤argv[1]、saveptr1和saveptr2,以理解strtok_r函數(shù)的工作方式。
Man Page的BUGS部分指出了用strtok和strtok_r函數(shù)需要注意的問(wèn)題:
這兩個(gè)函數(shù)要改寫(xiě)字符串以達(dá)到分割的效果
這兩個(gè)函數(shù)不能用于常量字符串,因?yàn)樵噲D改寫(xiě).rodata段會(huì)產(chǎn)生段錯(cuò)誤
在做了分割之后,字符串中的分隔符就被'\0'覆蓋了
strtok函數(shù)使用了靜態(tài)變量,它不是線程安全的,必要時(shí)應(yīng)該用可重入的strtok_r函數(shù),以后再詳細(xì)介紹“可重入”和“線程安全”這兩個(gè)概念
習(xí)題
1、出于練習(xí)的目的,strtok和strtok_r函數(shù)非常值得自己動(dòng)手實(shí)現(xiàn)一遍,在這個(gè)過(guò)程中不僅可以更深刻地理解這兩個(gè)函數(shù)的工作原理,也為以后理解“可重入”和“線程安全”這兩個(gè)重要概念打下基礎(chǔ)。
2、解析URL中的路徑和查詢(xún)字符串。動(dòng)態(tài)網(wǎng)頁(yè)的URL末尾通常帶有查詢(xún),例如:
比如上面第一個(gè)例子,是路徑部分,?號(hào)后面的complete=1&hl=zh-CN&ie=GB2312&q=linux&meta=是查詢(xún)字符串,由五個(gè)“key=value”形式的鍵值對(duì)(Key-value Pair)組成,以&隔開(kāi),有些鍵對(duì)應(yīng)的值可能是空字符串,比如這個(gè)例子中的鍵meta。
現(xiàn)在要求實(shí)現(xiàn)一個(gè)函數(shù),傳入一個(gè)帶查詢(xún)字符串的URL,首先檢查輸入格式的合法性,然后對(duì)URL進(jìn)行切分,將路徑部分和各鍵值對(duì)分別傳出,請(qǐng)仔細(xì)設(shè)計(jì)函數(shù)接口以便傳出這些字符串。如果函數(shù)中有動(dòng)態(tài)分配內(nèi)存的操作,還要另外實(shí)現(xiàn)一個(gè)釋放內(nèi)存的函數(shù)。完成之后,為自己設(shè)計(jì)的函數(shù)寫(xiě)一個(gè)Man Page。
總結(jié)
以上是生活随笔為你收集整理的c语言中 字符串常量的界定符,C字符串操作函数的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 用c语言实现存储和读取图片文件,C++实
- 下一篇: c 语言 string库,C语言编程必备