|
一、沉浸式学习 以学习一门语言为例: 而事实上,大多数人都没有这样的学习条件。 解决问题的方法是:- j; `( h9 W8 C# F& S9 U4 e" I9 y 例如:
对了,我作为英文的爱好者,一直想重启我的英文学习之路,后续想在公众号里记录一些英文相关的知识,请你们不要笑话我~~~ 二、字符串函数库:Simple Dynamic Strings1. 简介 Simple Dynamic Strings (简称 SDS) 是一个 C 语言字符串库,它增强了 C 语言字符串处理的能力。 设计 SDS 原本是为了满足设计者自身日常的 C 编程,后来又被转移到 Redis 中,在 Redis 中被广泛使用并对其进行了修改以适合于高性能操作。现在,它又被从 Redis 中提取出来的,并 fork 为一个独立项目。 只有 1500 行不到的代码,就能做到 3.2K 个 star,牛牛牛~~~ 它有什么优点?
源码链接: http://github.com/antirez/sds源码文件: sds.c# L3 y4 M# x" \; ?0 ~6 psdsalloc.h# B% q7 |2 X+ H( c" ]7 a# L sds.h) Q, Q; ^! g1 I& w6 A5 p testhelp.h 相关 API: sds sdsnewlen(const void *init, size_t initlen)sds sdsempty(void) 5 u) U2 ]6 _+ q0 [1 s sds sdsnew(const char *init) sds sdsdup(const sds s) void sdsfree(sds s) void sdsupdatelen(sds s) void sdsclear(sds s) 8 n# {8 K; R" i# |; Q* { sds sdsMakeRoomFor(sds s, size_t addlen) $ p3 O( M' U+ o ~ sds sdsRemoveFreeSpace(sds s) size_t sdsAllocSize(sds s) ) s! w) |0 z8 [/ R% m; I9 b; x void *sdsAllocPtr(sds s) + U. ~( Z" f% r" C. l% \* W: M( h7 M void sdsIncrLen(sds s, ssize_t incr) sds sdsgrowzero(sds s, size_t len) sds sdscatlen(sds s, const void *t, size_t len) 1 }- ?$ v+ ~3 W# p; E; L sds sdscat(sds s, const char *t) sds sdscatsds(sds s, const sds t) 3 p* n9 e/ T. B3 n3 b0 R* u: Z8 m" I sds sdscpylen(sds s, const char *t, size_t len) " j' L9 G" I Y' K" m( [0 { sds sdscpy(sds s, const char *t) x1 j9 C0 i4 |; `$ c; V9 w int sdsll2str(char *s, long long value) int sdsull2str(char *s, unsigned long long v) * K$ j2 r$ j4 T: o5 ? sds sdsfromlonglong(long long value) ' ~$ F1 ^8 j) m" D. h 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) $ ?( `1 _7 r+ f# T2 a$ V! E void sdsrange(sds s, ssize_t start, ssize_t end) 0 p9 b( w/ W+ q- s9 {- Y" M8 T) q void sdstolower(sds s) ) W/ N) m" Z }3 f void sdstoupper(sds s) int sdscmp(const sds s1, const sds s2) - A" [# b* }: c) e. J3 F- s$ A4 ^ 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) 8 A4 M+ b. A5 k1 z7 f% ]) S int hex_digit_to_int(char c) sds *sdssplitargs(const char *line, int *argc) - H! V5 m5 W. d) b$ \ sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) sds sdsjoin(char **argv, int argc, char *sep) 2 S3 k; c1 j: ?% J- C% M sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen)$ |5 e9 F' A. K( M 2. 比较常用的功能2.1 创建字符串 sdsnew() 和 sdsfree(): #include <stdio.h>#include "sds.h") c! V" K$ ?$ r/ Q- N' r$ G; O #include "sdsalloc.h" int main(void) {- z: ^* Z% L2 A7 N3 ~ sds mystr = sdsnew("Hello World!"); printf("%s\n", mystr);$ C3 V5 C# A3 l* h sdsfree(mystr);6 K- Z6 t" n* u* ?) P* @; k$ W& c }4 D) f) X; m4 L, `% ?) { 运行效果: $ gcc -o sdsdemo sds.c sdsdemo.c$ ./sdsdemo Hello World!* E- H* y* s# c/ b 看到了吗? printf 直接就可以打印 sds,这就是说 sds 本身就是 C 语言的字符串类型。 sds 的定义如下: typedef char *sds;也就是说,sds 是能兼容 libc 里字符串处理函数 (例如strcpy, strcat...)的。 当不再使用 sds 字符串时,就算是空串,也要通过 sdsfree 销毁字符串。 2.2 获取字符串长度sdsnewlen(): int main(void){ char buf[3];. y4 x( P2 O: [7 f/ j sds mystring; # {8 ]6 o- E# {7 h2 U; x buf[0] = 'A'; buf[1] = 'B'; buf[2] = 'C';* a4 O' B2 e W1 R& P% k mystring = sdsnewlen(buf,3); printf("%s of len %d\n", mystring, (int) sdslen(mystring)); } 运行效果: $ ./sdsdemo: ~9 `$ H$ d- s5 k4 ?0 PABC of len 38 a/ d" B8 a( v+ g' V 和 strlen() 有 2 点不同:
sdscat(): int main(void){ sds s = sdsempty(); s = sdscat(s, "Hello ");6 H$ _& E5 V+ U8 g7 \4 |0 E s = sdscat(s, "World!"); printf("%s\n", s); }( e6 a1 g2 w9 t% p1 `+ y 运行效果: $ ./sdsdemo* N' s; v# n$ G" T+ Y8 W0 rHello World! sdscat 接受的参数是以 NULL 结尾的字符串,如果想摆脱这个限制,可以用 sdscatsds()。 sdscatsds(): int main(void){9 r7 x: x8 H6 c% }8 C9 e: Q sds s1 = sdsnew("aaa");. R f3 u- ^! E F& a) O4 ~! r sds s2 = sdsnew("bbb"); s1 = sdscatsds(s1,s2); sdsfree(s2); printf("%s\n", s1); } 运行效果: $ ./sdsdemoaaabbb 2.4 扩展字符串长度 sdsgrowzero(): int main(void)1 L/ d6 g9 h; L8 W% n9 w& n1 |) I7 Q{" {' j( p+ f7 Y2 r4 a6 j9 h4 z1 N sds s = sdsnew("Hello");; S0 ]6 u1 s% e8 N& x s = sdsgrowzero(s,6);5 G) R# z, o- K6 ~7 P0 V( a" s s[5] = '!'; /* We are sure this is safe*/ printf("%s\n", s);% y- V9 X' O2 H y. o- w }: |' N+ |2 i5 y. ]) B 运行效果: $ ./sdsdemoHello! 2.5 格式化字符串 sdscatprintf(): int main(void)* F/ A& X7 z: R6 w' ~{5 r1 e, b( a3 b$ T sds s;4 v2 Q( i) ]+ {& o5 z' i int a = 10, b = 20; s = sdsnew("The sum is: "); s = sdscatprintf(s,"%d+%d = %d",a,b,a+b); printf("%s\n", s); }: k1 W/ z, G q r) Z; t; _$ N 运行效果: $ ./sdsdemoThe sum is: 10+20 = 30 2.6 截取字符串 sdstrim():去掉指定字符 int main(void){" \$ x' G# B5 A% ~ sds s = sdsnew(" my string\n\n "); sdstrim(s," \n"); printf("-%s-\n",s); } 运行效果: $ ./sdsdemo-my string- 去掉了空格和换行符。 sdsrange():截取指定范围内的字符串 int main(void){ p1 ?3 z9 ]) X sds s = sdsnew("Hello World!"); sdsrange(s,1,4); printf("-%s-\n", s); } 运行效果: $ ./sdsdemo-ello- 2.7 字符串分割 (Tokenization) sdssplitlen() 和 sdsfreesplitres(): int main(void)/ ?- v! Y6 v% R! m2 y* y{% ^- J4 U3 |1 x7 Y sds *tokens;+ j( p* V1 K$ I2 W int count, j; 1 M/ @2 u8 O& a3 t sds line = sdsnew("Hello World!"); tokens = sdssplitlen(line, sdslen(line)," ",1,&count); 6 y7 V, c) w% O* k) s; x5 a& R for (j = 0; j < count; j++) printf("%s\n", tokens[j]); sdsfreesplitres(tokens,count);9 V7 ~: f' m* S9 x3 k }* B" S; B4 S4 Q2 i, ^- Z, s3 Q sdssplitlen() 第 3和4 个参数指定分割符为空格。 运行效果: $ ./sdsdemo5 L% a( Y& `+ l, j* g3 WHello7 h- y. E* g/ S) B World! 2.8 字符串合并 (String joining) sdssplitlen() 和 sdsfreesplitres(): int main(void)9 l4 W. X$ H( v. M8 r& [{ char *tokens[3] = {"foo","bar","zap"};; m7 D1 `* O: m sds s = sdsjoin(tokens, 3, "|"); printf("%s\n", s);7 D% C* t% x2 z( p9 N& V }# l* o& L. C6 m, K/ N 运行效果: $ ./sdsdemo# v" N8 q7 z" rfoo|bar|zap 还有其他一些功能,用到再研究吧! 3. 简单了解一下内部实现在 SDSD 中,使用二进制前缀(头部) 来保存字符串相关的信息,该头部存储在 SDS 返回给用户的字符串的实际指针之前: +--------+-------------------------------+-----------+| Header | Binary safe C alike string... | Null term |, ~1 A" i- z$ G5 D! J7 F& z+ K +--------+-------------------------------+-----------+ | `-> Pointer returned to the user. 这个 Header 在代码中用结构体来描述,该结构体定义大致如下: struct sdshdr {[...] int len; char buf[];( k) f5 ?' x- g. |8 N; k };
假设你使用的字符串为 "HELLOWORLD",为了提升效率,SDS 可能会提前分配多一些空间,所以实际的内存布局如下: +------------+------------------------+-----------+---------------\| len | buf | H E L L O W O R L D \n | Null term | Free space \3 ^ O3 W5 {: C5 d- ]1 R7 @ +------------+------------------------+-----------+---------------\% f" b% E2 p, @9 l; E8 Y7 g# L; h | `-> Pointer returned to the user.7 e% Z6 q: Z8 A) q# ? 现在,我们来看一下 SDS 分配字符串的大致步骤: sds sdsnew(const char *init)initlen = (init == NULL) ? 0 : strlen(init); sdsnewlen(init, initlen); int hdrlen = sdsHdrSize(type); // 确定 Header 的长度5 _! h; t3 A9 }; z' z$ y* H sh = s_malloc(hdrlen+initlen+1); // 分配 Header + String + 1 个字节的空间7 b; e: i! [8 ~ s = (char*)sh+hdrlen; // 保存 C string 的地址 H1 j# _5 f1 K7 O3 T SDS_HDR_VAR(8,s); // 定义 struct sdshdr sh _5 U+ J( w9 w' E7 ?2 Y sh->len = initlen; // 初始化 struct sdshdr sh if (initlen && init) // 初始化 C string " E" E0 t: n. t; k6 [ memcpy(s, init, initlen);8 _- r4 S% t7 ~ + N3 ]6 m% V& W6 F5 \2 ] s[initlen] = '\0'; // 总是添加一个 NULL- J4 h* \6 R* ~/ Y; Z9 W8 D4 o3 t Q return s; // 返回 C string 其他的 SDS API 是如何实现的,就留给大家自行分析了。 4. 相关参考-《Linux程序设计》,6,7.1 章节 -《C primer plus》,11,12 章节 -《C 和指针》,9 章节 -《Linux 系统编程》,9 章节 -《C专家编程》,7.5 章节 -《C和C++程序员面试秘笈》,4 章节 / {- R0 W# ]4 c7 R |
微信公众号
手机版