C語言:優(yōu)雅的字符串函數(shù)庫
一、沉浸式學(xué)習(xí)
以學(xué)習(xí)一門語言為例:
大多數(shù)人都持有一種觀念,要真正學(xué)好一門語言必須得去所學(xué)語言當(dāng)?shù)貙W(xué)習(xí)或生活一段時(shí)間。
而事實(shí)上,大多數(shù)人都沒有這樣的學(xué)習(xí)條件。
解決問題的方法是:
自行改造環(huán)境,為自己創(chuàng)造沉浸式的學(xué)習(xí)環(huán)境。
例如:
-
看新語言的電影;
-
更改手機(jī)、電腦的語言設(shè)置;
-
看新語言的文檔和書籍;
-
用新語言寫 todo list;
-
用新語言來學(xué)習(xí)自己需要的專業(yè)知識(shí);
-
翻譯新語言的文檔并分享;
-
逛所學(xué)語言的論壇和網(wǎng)站;
-
用新語言寫代碼注釋 / commit message / README / issue;
-
...
對了,我作為英文的愛好者,一直想重啟我的英文學(xué)習(xí)之路,后續(xù)想在公眾號(hào)里記錄一些英文相關(guān)的知識(shí),請你們不要笑話我~~~
二、字符串函數(shù)庫:Simple Dynamic Strings
1. 簡介
Simple Dynamic Strings (簡稱 SDS) 是一個(gè) C 語言字符串庫,它增強(qiáng)了 C 語言字符串處理的能力。
設(shè)計(jì) SDS 原本是為了滿足設(shè)計(jì)者自身日常的 C 編程,后來又被轉(zhuǎn)移到 Redis 中,在 Redis 中被廣泛使用并對其進(jìn)行了修改以適合于高性能操作。現(xiàn)在,它又被從 Redis 中提取出來的,并 fork 為一個(gè)獨(dú)立項(xiàng)目。
只有 1500 行不到的代碼,就能做到 3.2K 個(gè) star,牛牛牛~~~
它有什么優(yōu)點(diǎn)?
-
使用更簡單;
-
二進(jìn)制安全;
-
效率更高;
-
與 C 字符串函數(shù)兼容;
源碼鏈接:
https://github.com/antirez/sds
源碼文件:
sds.c
sdsalloc.h
sds.h
testhelp.h
相關(guān) API:
sds sdsnewlen(const void *init, size_t initlen)
sds sdsempty(void)
sds sdsnew(const char *init)
sds sdsdup(const sds s)
void sdsfree(sds s)
void sdsupdatelen(sds s)
void sdsclear(sds s)
sds sdsMakeRoomFor(sds s, size_t addlen)
sds sdsRemoveFreeSpace(sds s)
size_t sdsAllocSize(sds s)
void *sdsAllocPtr(sds s)
void sdsIncrLen(sds s, ssize_t incr)
sds sdsgrowzero(sds s, size_t len)
sds sdscatlen(sds s, const void *t, size_t len)
sds sdscat(sds s, const char *t)
sds sdscatsds(sds s, const sds t)
sds sdscpylen(sds s, const char *t, size_t len)
sds sdscpy(sds s, const char *t)
int sdsll2str(char *s, long long value)
int sdsull2str(char *s, unsigned long long v)
sds sdsfromlonglong(long long value)
sds sdscatvprintf(sds s, const char *fmt, va_list ap)
sds sdscatprintf(sds s, const char *fmt, ...)
sds sdscatfmt(sds s, char const *fmt, ...)
sds sdstrim(sds s, const char *cset)
void sdsrange(sds s, ssize_t start, ssize_t end)
void sdstolower(sds s)
void sdstoupper(sds s)
int sdscmp(const sds s1, const sds s2)
sds *sdssplitlen(const char *s, ssize_t len, const char *sep, int seplen, int *count)
void sdsfreesplitres(sds *tokens, int count)
sds sdscatrepr(sds s, const char *p, size_t len)
int is_hex_digit(char c)
int hex_digit_to_int(char c)
sds *sdssplitargs(const char *line, int *argc)
sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen)
sds sdsjoin(char **argv, int argc, char *sep)
sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen)
2. 比較常用的功能
2.1 創(chuàng)建字符串
sdsnew() 和 sdsfree():
#include <stdio.h>
#include "sds.h"
#include "sdsalloc.h"
int main(void)
{
sds mystr = sdsnew("Hello World!");
printf("%s\n", mystr);
sdsfree(mystr);
}
運(yùn)行效果:
$ gcc -o sdsdemo sds.c sdsdemo.c
$ ./sdsdemo
Hello World!
看到了嗎?
printf 直接就可以打印 sds,這就是說 sds 本身就是 C 語言的字符串類型。
sds 的定義如下:
typedef char *sds;
也就是說,sds 是能兼容 libc 里字符串處理函數(shù) (例如strcpy, strcat...)的。
當(dāng)不再使用 sds 字符串時(shí),就算是空串,也要通過 sdsfree 銷毀字符串。
2.2 獲取字符串長度
sdsnewlen():
int main(void)
{
char buf[3];
sds mystring;
buf[0] = 'A';
buf[1] = 'B';
buf[2] = 'C';
mystring = sdsnewlen(buf,3);
printf("%s of len %d\n", mystring, (int) sdslen(mystring));
}
運(yùn)行效果:
$ ./sdsdemo
ABC of len 3
和 strlen() 有 2 點(diǎn)不同:
-
運(yùn)行時(shí)長固定,sds 內(nèi)部有數(shù)據(jù)結(jié)構(gòu)保存著字符串的長度;
-
長度與字符串內(nèi)是否有 NULL 無關(guān);
2.3 拼接字符串
sdscat():
int main(void)
{
sds s = sdsempty();
s = sdscat(s, "Hello ");
s = sdscat(s, "World!");
printf("%s\n", s);
}
運(yùn)行效果:
$ ./sdsdemo
Hello World!
sdscat 接受的參數(shù)是以 NULL 結(jié)尾的字符串,如果想擺脫這個(gè)限制,可以用 sdscatsds()。
sdscatsds():
int main(void)
{
sds s1 = sdsnew("aaa");
sds s2 = sdsnew("bbb");
s1 = sdscatsds(s1,s2);
sdsfree(s2);
printf("%s\n", s1);
}
運(yùn)行效果:
$ ./sdsdemo
aaabbb
2.4 擴(kuò)展字符串長度
sdsgrowzero():
int main(void)
{
sds s = sdsnew("Hello");
s = sdsgrowzero(s,6);
s[5] = '!'; /* We are sure this is safe*/
printf("%s\n", s);
}
運(yùn)行效果:
$ ./sdsdemo
Hello!
2.5 格式化字符串
sdscatprintf():
int main(void)
{
sds s;
int a = 10, b = 20;
s = sdsnew("The sum is: ");
s = sdscatprintf(s,"%d+%d = %d",a,b,a+b);
printf("%s\n", s);
}
運(yùn)行效果:
$ ./sdsdemo
The sum is: 10+20 = 30
2.6 截取字符串
sdstrim():去掉指定字符
int main(void)
{
sds s = sdsnew(" my string\n\n ");
sdstrim(s," \n");
printf("-%s-\n",s);
}
運(yùn)行效果:
$ ./sdsdemo
-my string-
去掉了空格和換行符。
sdsrange():截取指定范圍內(nèi)的字符串
int main(void)
{
sds s = sdsnew("Hello World!");
sdsrange(s,1,4);
printf("-%s-\n", s);
}
運(yùn)行效果:
$ ./sdsdemo
-ello-
2.7 字符串分割 (Tokenization)
sdssplitlen() 和 sdsfreesplitres():
int main(void)
{
sds *tokens;
int count, j;
sds line = sdsnew("Hello World!");
tokens = sdssplitlen(line, sdslen(line)," ",1,&count);
for (j = 0; j < count; j++)
printf("%s\n", tokens[j]);
sdsfreesplitres(tokens,count);
}
sdssplitlen() 第 3和4 個(gè)參數(shù)指定分割符為空格。
運(yùn)行效果:
$ ./sdsdemo
Hello
World!
2.8 字符串合并 (String joining)
sdssplitlen() 和 sdsfreesplitres():
int main(void)
{
char *tokens[3] = {"foo","bar","zap"};
sds s = sdsjoin(tokens, 3, "|");
printf("%s\n", s);
}
運(yùn)行效果:
$ ./sdsdemo
foo|bar|zap
還有其他一些功能,用到再研究吧!
3. 簡單了解一下內(nèi)部實(shí)現(xiàn)
在 SDSD 中,使用二進(jìn)制前綴(頭部) 來保存字符串相關(guān)的信息,該頭部存儲(chǔ)在 SDS 返回給用戶的字符串的實(shí)際指針之前:
+--------+-------------------------------+-----------+
| Header | Binary safe C alike string... | Null term |
+--------+-------------------------------+-----------+
|
`-> Pointer returned to the user.
這個(gè) Header 在代碼中用結(jié)構(gòu)體來描述,該結(jié)構(gòu)體定義大致如下:
struct sdshdr {
[...]
int len;
char buf[];
};
-
len 存儲(chǔ)的是字符串長度;
-
buf 指向緊隨其后的字符串首地址;
假設(shè)你使用的字符串為 "HELLOWORLD",為了提升效率,SDS 可能會(huì)提前分配多一些空間,所以實(shí)際的內(nèi)存布局如下:
+------------+------------------------+-----------+---------------\
| len | buf | H E L L O W O R L D \n | Null term | Free space \
+------------+------------------------+-----------+---------------\
|
`-> Pointer returned to the user.
現(xiàn)在,我們來看一下 SDS 分配字符串的大致步驟:
sds sdsnew(const char *init)
initlen = (init == NULL) ? 0 : strlen(init);
sdsnewlen(init, initlen);
int hdrlen = sdsHdrSize(type); // 確定 Header 的長度
sh = s_malloc(hdrlen+initlen+1); // 分配 Header + String + 1 個(gè)字節(jié)的空間
s = (char*)sh+hdrlen; // 保存 C string 的地址
SDS_HDR_VAR(8,s); // 定義 struct sdshdr sh
sh->len = initlen; // 初始化 struct sdshdr sh
if (initlen && init) // 初始化 C string
memcpy(s, init, initlen);
s[initlen] = '\0'; // 總是添加一個(gè) NULL
return s; // 返回 C string
其他的 SDS API 是如何實(shí)現(xiàn)的,就留給大家自行分析了。
4. 相關(guān)參考
-《Linux程序設(shè)計(jì)》,6,7.1 章節(jié)
-《C primer plus》,11,12 章節(jié)
-《C 和指針》,9 章節(jié)
-《Linux 系統(tǒng)編程》,9 章節(jié)
-《C專家編程》,7.5 章節(jié)
-《C和C++程序員面試秘笈》,4 章節(jié)
-END-
本文授權(quán)轉(zhuǎn)載自嵌入式Hacker,作者:吳偉東Jack
推薦閱讀
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場,如有問題,請聯(lián)系我們,謝謝!