
哈喽,周末愉快。我是老吴,继续记录我的学习心得。
一、沉浸式学习
以学习一门语言为例:
大多数人都持有一种观念,要真正学好一门语言必须得去所学语言当地学习或生活一段时间。
而事实上,大多数人都没有这样的学习条件。
解决方法是:
自行改造环境,为自己创造沉浸式的学习环境。
例如:
对了,我作为英文的爱好者,一直想重启我的英文学习之路,后续想在公众号里记录一些英文相关的知识,请你们不要笑话我~
二、字符串函数库:Simple Dynamic Strings
1. 简介
Simple Dynamic Strings (简称 SDS) 是一个 C 语言字符串库,它增强了 C 语言字符串处理的能力。
设计 SDS 原本是为了满足设计者自身日常的 C 编程,后来又被转移到 Redis 中,在 Redis 中被广泛使用并对其进行了修改以适合于高性能操作。 现在,它又被从 Redis 中提取出来的,并 fork 为一个独立项目。
只有 1500 行不到的代码,就能做到 3.2K 个 star,牛牛牛~
优点:
使用更简单;
二进制安全;
效率更高;
与 C 字符串函数兼容;
源码链接:
1 | https://github.com/antirez/sds |
源码文件:
1 | sds.c |
2 | sdsalloc.h |
3 | sds.h |
4 | testhelp.h |
相关 API:
1 | sds sdsnewlen(const void *init, size_t initlen) |
2 | sds sdsempty(void) |
3 | sds sdsnew(const char *init) |
4 | sds sdsdup(const sds s) |
5 | void sdsfree(sds s) |
6 | void sdsupdatelen(sds s) |
7 | void sdsclear(sds s) |
8 | sds sdsMakeRoomFor(sds s, size_t addlen) |
9 | sds sdsRemoveFreeSpace(sds s) |
10 | size_t sdsAllocSize(sds s) |
11 | void *sdsAllocPtr(sds s) |
12 | void sdsIncrLen(sds s, ssize_t incr) |
13 | sds sdsgrowzero(sds s, size_t len) |
14 | sds sdscatlen(sds s, const void *t, size_t len) |
15 | sds sdscat(sds s, const char *t) |
16 | sds sdscatsds(sds s, const sds t) |
17 | sds sdscpylen(sds s, const char *t, size_t len) |
18 | sds sdscpy(sds s, const char *t) |
19 | int sdsll2str(char *s, long long value) |
20 | int sdsull2str(char *s, unsigned long long v) |
21 | sds sdsfromlonglong(long long value) |
22 | sds sdscatvprintf(sds s, const char *fmt, va_list ap) |
23 | sds sdscatprintf(sds s, const char *fmt, ...) |
24 | sds sdscatfmt(sds s, char const *fmt, ...) |
25 | sds sdstrim(sds s, const char *cset) |
26 | void sdsrange(sds s, ssize_t start, ssize_t end) |
27 | void sdstolower(sds s) |
28 | void sdstoupper(sds s) |
29 | int sdscmp(const sds s1, const sds s2) |
30 | sds *sdssplitlen(const char *s, ssize_t len, const char *sep, int seplen, int *count) |
31 | void sdsfreesplitres(sds *tokens, int count) |
32 | sds sdscatrepr(sds s, const char *p, size_t len) |
33 | int is_hex_digit(char c) |
34 | int hex_digit_to_int(char c) |
35 | sds *sdssplitargs(const char *line, int *argc) |
36 | sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) |
37 | sds sdsjoin(char **argv, int argc, char *sep) |
38 | sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen) |
2. 比较常用的功能
2.1 创建字符串
sdsnew() 和 sdsfree():
1 | #include <stdio.h> |
2 | #include "sds.h" |
3 | #include "sdsalloc.h" |
4 |
|
5 | int main(void) |
6 | { |
7 | sds mystr = sdsnew("Hello World!"); |
8 | printf("%s\n", mystr); |
9 | sdsfree(mystr); |
10 | } |
运行效果:
1 | $ gcc -o sdsdemo sds.c sdsdemo.c |
2 | $ ./sdsdemo |
3 | Hello World! |
看到了吗?
printf 直接就可以打印 sds,这就是说 sds 本身就是 C 语言的字符串类型。
sds 的定义如下:
也就是说,sds 是能兼容 libc 里字符串处理函数 (例如strcpy, strcat…)的。
当不再使用 sds 字符串时,就是是空串,也要通过 sdsfree 销毁字符串。
2.2 获取字符串长度
sdsnewlen():
1 | int main(void) |
2 | { |
3 | char buf[3]; |
4 | sds mystring; |
5 |
|
6 | buf[0] = 'A'; |
7 | buf[1] = 'B'; |
8 | buf[2] = 'C'; |
9 | mystring = sdsnewlen(buf,3); |
10 | printf("%s of len %d\n", mystring, (int) sdslen(mystring)); |
11 | } |
运行效果:
1 | $ ./sdsdemo |
2 | ABC of len 3 |
和 strlen() 有 2 点不同:
2.3 拼接字符串
sdscat():
1 | int main(void) |
2 | { |
3 | sds s = sdsempty(); |
4 | s = sdscat(s, "Hello "); |
5 | s = sdscat(s, "World!"); |
6 | printf("%s\n", s); |
7 | } |
运行效果:
1 | $ ./sdsdemo |
2 | Hello World! |
sdscat 接受的参数是以 NULL 结尾的字符串,如果想摆脱这个限制,可以用 sdscatsds()。
sdscatsds():
1 | int main(void) |
2 | { |
3 | sds s1 = sdsnew("aaa"); |
4 | sds s2 = sdsnew("bbb"); |
5 | s1 = sdscatsds(s1,s2); |
6 | sdsfree(s2); |
7 | printf("%s\n", s1); |
8 | } |
运行效果:
2.4 扩展字符串长度
sdsgrowzero():
1 | int main(void) |
2 | { |
3 | sds s = sdsnew("Hello"); |
4 | s = sdsgrowzero(s,6); |
5 | s[5] = '!'; |
6 | printf("%s\n", s); |
7 | } |
运行效果:
2.5 格式化字符串
sdscatprintf():
1 | int main(void) |
2 | { |
3 | sds s; |
4 | int a = 10, b = 20; |
5 | s = sdsnew("The sum is: "); |
6 | s = sdscatprintf(s,"%d+%d = %d",a,b,a+b); |
7 | printf("%s\n", s); |
8 | } |
运行效果:
1 | $ ./sdsdemo |
2 | The sum is: 10+20 = 30 |
2.6 截取字符串
sdstrim():去掉指定字符
1 | int main(void) |
2 | { |
3 | sds s = sdsnew(" my string\n\n "); |
4 | sdstrim(s," \n"); |
5 | printf("-%s-\n",s); |
6 | } |
运行效果:
去掉了空格和换行符。
sdsrange():截取指定范围内的字符串
1 | int main(void) |
2 | { |
3 | sds s = sdsnew("Hello World!"); |
4 | sdsrange(s,1,4); |
5 | printf("-%s-\n", s); |
6 | } |
运行效果:
2.7 字符串分割 (Tokenization)
sdssplitlen() 和 sdsfreesplitres():
1 | int main(void) |
2 | { |
3 | sds *tokens; |
4 | int count, j; |
5 |
|
6 | sds line = sdsnew("Hello World!"); |
7 | tokens = sdssplitlen(line, sdslen(line)," ",1,&count); |
8 |
|
9 | for (j = 0; j < count; j++) |
10 | printf("%s\n", tokens[j]); |
11 | sdsfreesplitres(tokens,count); |
12 | } |
sdssplitlen() 第 3和4 个参数指定分割符为空格。
运行效果:
1 | $ ./sdsdemo |
2 | Hello |
3 | World! |
2.8 字符串合并 (String joining)
sdssplitlen() 和 sdsfreesplitres():
1 | int main(void) |
2 | { |
3 | char *tokens[3] = {"foo","bar","zap"}; |
4 | sds s = sdsjoin(tokens, 3, "|"); |
5 | printf("%s\n", s); |
6 | } |
运行效果:
还有其他一些功能,用到再研究吧!
3. 简单了解一下内部实现
在 SDSD 中,使用二进制前缀(头部) 来保存字符串相关的信息,该头部存储在 SDS 返回给用户的字符串的实际指针之前:
1 | +--------+-------------------------------+-----------+ |
2 | | Header | Binary safe C alike string... | Null term | |
3 | +--------+-------------------------------+-----------+ |
4 | | |
5 | `-> Pointer returned to the user. |
这个 Header 在代码中用结构体来描述,该结构体定义大致如下:
1 | struct sdshdr { |
2 | [...] |
3 | int len; |
4 | char buf[]; |
5 | }; |
len 存储的是字符串长度;
buf 指向紧随其后的字符串首地址;
假设你使用的字符串为 “HELLOWORLD”,为了提升效率,SDS 可能会提前分配多一些空间,所以实际的内存布局如下:
1 | +------------+------------------------+-----------+---------------\ |
2 | | len | buf | H E L L O W O R L D \n | Null term | Free space \ |
3 | +------------+------------------------+-----------+---------------\ |
4 | | |
5 | `-> Pointer returned to the user. |
现在,我们来看一下 SDS 分配字符串的大致步骤:
1 | sds sdsnew(const char *init) |
2 | initlen = (init == NULL) ? 0 : strlen(init); |
3 | sdsnewlen(init, initlen); |
4 | int hdrlen = sdsHdrSize(type); |
5 | sh = s_malloc(hdrlen+initlen+1); |
6 |
|
7 | s = (char*)sh+hdrlen; |
8 | SDS_HDR_VAR(8,s); |
9 | sh->len = initlen; |
10 |
|
11 | if (initlen && init) |
12 | memcpy(s, init, initlen); |
13 | |
14 | s[initlen] = '\0'; |
15 | return s; |
其他的 SDS API 是如何实现的,就留给大家自行分析了。
4. 相关参考
-《Linux程序设计》,6,7.1 章节
-《C primer plus》,11,12 章节
-《C 和指针》,9 章节
-《Linux 系统边城》,9 章节
-《C专家编程》,7.5 章节
-《C和C++程序员面试秘笈》,4 章节
三、思考技术,也要思考人生
学习技术,更要学习如何生活。
你和我各有一个苹果,如果我们交换苹果的话,我们还是只有一个苹果。但当你和我各有一个想法,我们交换想法的话,我们就都有两个想法了。

对 嵌入式系统 (Linux、RTOS、OpenWrt、Android) 和 开源软件 感兴趣,想和更多人互相交流学习,请关注公众号:嵌入式Hacker。
觉得文章对你有价值的话,不妨点个 在看和点赞 哦。