
一、沉浸式学习 以学习一门语言为例: 而事实上,大多数人都没有这样的学习条件。 解决问题的方法是:7 c1 \# C2 r' V% R 例如:
对了,我作为英文的爱好者,一直想重启我的英文学习之路,后续想在公众号里记录一些英文相关的知识,请你们不要笑话我~~~ 二、字符串函数库:Simple Dynamic Strings1. 简介 Simple Dynamic Strings (简称 SDS) 是一个 C 语言字符串库,它增强了 C 语言字符串处理的能力。 设计 SDS 原本是为了满足设计者自身日常的 C 编程,后来又被转移到 Redis 中,在 Redis 中被广泛使用并对其进行了修改以适合于高性能操作。现在,它又被从 Redis 中提取出来的,并 fork 为一个独立项目。 只有 1500 行不到的代码,就能做到 3.2K 个 star,牛牛牛~~~ 它有什么优点?
源码链接: http://github.com/antirez/sds" s$ h4 n- G4 j% c1 G2 m. B源码文件: sds.c( c2 O& D" B0 s8 `" S2 ysdsalloc.h sds.h' D/ R1 z4 {: L, z* p- x4 t testhelp.h) A/ h. j3 u. w% C! P0 Y. V 相关 API: sds sdsnewlen(const void *init, size_t initlen)sds sdsempty(void) % a' ^" }5 y. F6 i5 R sds sdsnew(const char *init) : z8 x7 c- t) ?, e5 u6 J$ l6 y" [ sds sdsdup(const sds s) " K) x) X% B- |0 }) ] void sdsfree(sds s) void sdsupdatelen(sds s) ; c, Q% v5 d, V- } void sdsclear(sds s) sds sdsMakeRoomFor(sds s, size_t addlen) # f/ Z+ k& X: a; V. o7 f' M sds sdsRemoveFreeSpace(sds s) size_t sdsAllocSize(sds s) V; E# i2 D- p8 `) D 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) $ S d. k: W: g7 Z7 f! o) V sds sdscpy(sds s, const char *t) + V; z& \ x; |- o- Y 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, ...) # d- o- B& t0 {: l2 k6 _ sds sdscatfmt(sds s, char const *fmt, ...) " T8 m) l0 i6 y9 |& q sds sdstrim(sds s, const char *cset) / Z' I% v1 F1 P6 W1 c/ }" V void sdsrange(sds s, ssize_t start, ssize_t end) void sdstolower(sds s) 2 V/ K% V* R/ I) |/ R& y$ y/ y7 ~ void sdstoupper(sds s) & Q2 |2 s- j, Q+ A) X8 k int sdscmp(const sds s1, const sds s2) 7 H# j) v" Q9 N0 h+ _$ D+ Z sds *sdssplitlen(const char *s, ssize_t len, const char *sep, int seplen, int *count) void sdsfreesplitres(sds *tokens, int count) & w0 I* b! J2 p" L3 F; F; O. J sds sdscatrepr(sds s, const char *p, size_t len) , i, ~6 B7 n) ~3 }' G* _0 n* O int is_hex_digit(char c) int hex_digit_to_int(char c) 0 U2 U J. ^6 K3 x* D) p3 c. E sds *sdssplitargs(const char *line, int *argc) ) ?' |$ L. o0 N' Q9 ^ sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) / X3 g4 L! X/ x6 x sds sdsjoin(char **argv, int argc, char *sep) h3 I; d+ _5 q+ u+ p, v2 }, z sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen) t6 o* J+ a% T/ o3 M 2. 比较常用的功能2.1 创建字符串 sdsnew() 和 sdsfree(): #include <stdio.h>; e8 S* X/ N7 a1 Z3 I$ W#include "sds.h", Z I# [0 @- A9 g6 \ #include "sdsalloc.h"/ O" `( W! D% n9 Y4 H/ T7 s ! A0 Q; A. J% [. b$ ~( A) g int main(void) { sds mystr = sdsnew("Hello World!"); d8 j# j- k, i7 t {( @/ k0 k printf("%s\n", mystr); sdsfree(mystr);8 B& L8 _( ^# K/ R9 m( [ }8 N% n7 z) s- V2 j2 T" H1 B% X: | 运行效果: $ gcc -o sdsdemo sds.c sdsdemo.c7 q9 C3 c+ S# M# R$ ./sdsdemo: Q) }% h% G4 }4 Q5 T Hello World!0 m8 Q3 u0 S9 v1 g* Z$ S5 h9 M( b 看到了吗? printf 直接就可以打印 sds,这就是说 sds 本身就是 C 语言的字符串类型。 sds 的定义如下: typedef char *sds;: c0 \8 h. c: j' u1 x也就是说,sds 是能兼容 libc 里字符串处理函数 (例如strcpy, strcat...)的。 当不再使用 sds 字符串时,就算是空串,也要通过 sdsfree 销毁字符串。 2.2 获取字符串长度sdsnewlen(): int main(void){: v, u- j& S8 E5 f0 g$ H! c1 } char buf[3]; sds mystring;! P a) L6 N( d6 E6 B) t ( p9 s* ~8 D$ t/ W buf[0] = 'A'; buf[1] = 'B';+ ?7 a% x9 ?' O9 C9 x d4 Y( K buf[2] = 'C';" x0 ~8 N+ x7 u; L+ w+ V! V; c1 v" h mystring = sdsnewlen(buf,3); printf("%s of len %d\n", mystring, (int) sdslen(mystring));* J) Z0 `- V% G* s: j% s } 运行效果: $ ./sdsdemoABC of len 3 和 strlen() 有 2 点不同:
sdscat(): int main(void)4 c# F0 t3 @: u/ }{" m: j, B5 [. e sds s = sdsempty();2 C7 O4 w; I& U x6 \ s = sdscat(s, "Hello "); s = sdscat(s, "World!"); printf("%s\n", s);( u! l# n5 ^- ~4 B6 p } 运行效果: $ ./sdsdemoHello World! sdscat 接受的参数是以 NULL 结尾的字符串,如果想摆脱这个限制,可以用 sdscatsds()。 sdscatsds(): int main(void){ sds s1 = sdsnew("aaa"); sds s2 = sdsnew("bbb"); s1 = sdscatsds(s1,s2); sdsfree(s2);/ x$ t* b5 ]8 s. J printf("%s\n", s1); }4 T4 R6 g( D& Q3 k 运行效果: $ ./sdsdemo# p+ a( N: I- [aaabbb 2.4 扩展字符串长度 sdsgrowzero(): int main(void){ sds s = sdsnew("Hello");2 p: S6 z% q7 s7 L s = sdsgrowzero(s,6); s[5] = '!'; /* We are sure this is safe*/! u3 ]5 U, r$ d& r& l2 [' W( o printf("%s\n", s); } 运行效果: $ ./sdsdemoHello! 2.5 格式化字符串 sdscatprintf(): int main(void){" h( C" w' |- o% V+ Z5 E sds s;2 f3 y+ z5 B! w8 D: Q int a = 10, b = 20;4 s5 ^( K1 \: w. w1 l s = sdsnew("The sum is: ");% T, A+ h" ?$ B/ k s = sdscatprintf(s,"%d+%d = %d",a,b,a+b);! X( Q5 u& n0 g u& t I0 s/ @ printf("%s\n", s); }7 Q, R2 @5 [; L- V8 p 运行效果: $ ./sdsdemo2 s# e4 E5 k7 z. J {# U/ I7 [9 W' sThe sum is: 10+20 = 30+ _+ w; i, Y9 n0 T, o( F, r 2.6 截取字符串 sdstrim():去掉指定字符 int main(void)' ?* a* b2 A& J1 G2 {{1 c0 {+ l3 @' C$ i& ^ | sds s = sdsnew(" my string\n\n ");( F& t6 R, r" x! _ sdstrim(s," \n"); printf("-%s-\n",s);; l9 f3 V! g7 m } 运行效果: $ ./sdsdemo9 I. [" s- P, d5 Y- e! S-my string- 去掉了空格和换行符。 sdsrange():截取指定范围内的字符串 int main(void)5 j* X- o( E: E: k; }{" Q2 {. l. C6 r' g; x6 d* Y% F/ { sds s = sdsnew("Hello World!");; g6 t) E) G, z$ _# j$ G8 k sdsrange(s,1,4); printf("-%s-\n", s); }" t, U1 h) _7 N$ O# O0 m% t 运行效果: $ ./sdsdemo- G- ?6 j; h" L4 m/ G. Q* @4 c& C-ello-$ k) \, W* E* F6 g 2.7 字符串分割 (Tokenization) sdssplitlen() 和 sdsfreesplitres(): int main(void)& y: z* r( B% a; O5 h& m+ y6 ^{ sds *tokens;2 F" V- o1 R( q int count, j;# A4 p0 c# a! t & l# Q, ^ J4 b, Q6 I; M; g sds line = sdsnew("Hello World!"); tokens = sdssplitlen(line, sdslen(line)," ",1,&count); x/ ^3 n. ?) _' M$ P2 {! z" W for (j = 0; j < count; j++)7 M& c- s$ s5 K6 f: Z( { printf("%s\n", tokens[j]);1 `" M7 y' a4 j) W; E, Z5 z sdsfreesplitres(tokens,count);2 ^- C% H, p4 Z } sdssplitlen() 第 3和4 个参数指定分割符为空格。 运行效果: $ ./sdsdemo Z) f9 W- ^" M9 t4 {9 l# VHello World!3 r' A4 i4 i7 }4 U7 R 2.8 字符串合并 (String joining) sdssplitlen() 和 sdsfreesplitres(): int main(void)) O$ j& k, w) `7 L9 F( v) f{ char *tokens[3] = {"foo","bar","zap"}; sds s = sdsjoin(tokens, 3, "|");8 v* ^9 m& F! i: l# J: c; e printf("%s\n", s); }* @' H {! Z- e 运行效果: $ ./sdsdemofoo|bar|zap5 E, M' l) W8 p0 c. K) T6 \2 \0 Q 还有其他一些功能,用到再研究吧! 3. 简单了解一下内部实现在 SDSD 中,使用二进制前缀(头部) 来保存字符串相关的信息,该头部存储在 SDS 返回给用户的字符串的实际指针之前: +--------+-------------------------------+-----------+| Header | Binary safe C alike string... | Null term | +--------+-------------------------------+-----------+" \5 v5 V5 b1 K$ f) u |# U" x4 M+ e) U: V5 a `-> Pointer returned to the user.; k" ^: s% u+ ^, f9 w6 b 这个 Header 在代码中用结构体来描述,该结构体定义大致如下: struct sdshdr {[...] int len;8 l& G, s: K% F! F% e: [8 R char buf[];. b# X7 ^# T0 ?+ z- }, q3 c" d$ M };
假设你使用的字符串为 "HELLOWORLD",为了提升效率,SDS 可能会提前分配多一些空间,所以实际的内存布局如下: +------------+------------------------+-----------+---------------\5 m( F8 b" X* T| len | buf | H E L L O W O R L D \n | Null term | Free space \ +------------+------------------------+-----------+---------------\! ?: p5 O2 s/ x | `-> Pointer returned to the user. 现在,我们来看一下 SDS 分配字符串的大致步骤: sds sdsnew(const char *init)initlen = (init == NULL) ? 0 : strlen(init);8 S0 G) F, W( X7 q, A) a$ U sdsnewlen(init, initlen); int hdrlen = sdsHdrSize(type); // 确定 Header 的长度9 j) t7 u: u8 ?' @/ O sh = s_malloc(hdrlen+initlen+1); // 分配 Header + String + 1 个字节的空间 & ]+ B# g* Z6 f% M* i2 ~$ z s = (char*)sh+hdrlen; // 保存 C string 的地址 SDS_HDR_VAR(8,s); // 定义 struct sdshdr sh3 f: C$ G& L5 q1 O; p7 _ sh->len = initlen; // 初始化 struct sdshdr sh% T3 W& F! `. ~. }8 g) i- V 3 [' H& c* A8 L8 ]1 ^# q: B$ g6 p if (initlen && init) // 初始化 C string : a7 K/ d6 w O6 M1 ^" Q( `$ K memcpy(s, init, initlen); 3 X3 l' d. f8 O- h s[initlen] = '\0'; // 总是添加一个 NULL return s; // 返回 C string2 q/ [! G2 X, T 其他的 SDS API 是如何实现的,就留给大家自行分析了。 4. 相关参考-《Linux程序设计》,6,7.1 章节 -《C primer plus》,11,12 章节 -《C 和指针》,9 章节 -《Linux 系统编程》,9 章节 -《C专家编程》,7.5 章节 -《C和C++程序员面试秘笈》,4 章节 |