你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

C语言——字符串函数库

[复制链接]
gaosmile 发布时间:2020-8-31 23:09
一、沉浸式学习

以学习一门语言为例:
. V& M7 n. i1 y2 L大多数人都持有一种观念,要真正学好一门语言必须得去所学语言当地学习或生活一段时间。

而事实上,大多数人都没有这样的学习条件。

解决问题的方法是:7 c1 \# C2 r' V% R
自行改造环境,为自己创造沉浸式的学习环境。

例如:

  • 看新语言的电影;

  • 更改手机、电脑的语言设置;

  • 看新语言的文档和书籍;

  • 用新语言写 todo list;

  • 用新语言来学习自己需要的专业知识;

  • 翻译新语言的文档并分享;

  • 逛所学语言的论坛和网站;

  • 用新语言写代码注释 / commit message / README / issue;

  • ...

    . N& \; O% G1 P' F7 W6 E; z+ H; o6 a

对了,我作为英文的爱好者,一直想重启我的英文学习之路,后续想在公众号里记录一些英文相关的知识,请你们不要笑话我~~~


二、字符串函数库:Simple Dynamic Strings1. 简介

Simple Dynamic Strings (简称 SDS) 是一个 C 语言字符串库,它增强了 C 语言字符串处理的能力。

设计 SDS 原本是为了满足设计者自身日常的 C 编程,后来又被转移到 Redis 中,在 Redis 中被广泛使用并对其进行了修改以适合于高性能操作。现在,它又被从 Redis 中提取出来的,并 fork 为一个独立项目。

只有 1500 行不到的代码,就能做到 3.2K 个 star,牛牛牛~~~

它有什么优点?

  • 使用更简单;

  • 二进制安全;

  • 效率更高;

  • 与 C 字符串函数兼容;

    : J( f% ^* ~8 J, ]

源码链接:

http://github.com/antirez/sds" s$ h4 n- G4 j% c1 G2 m. B

源码文件:

sds.c( c2 O& D" B0 s8 `" S2 y
sdsalloc.h
  g% @" t* e( g. ]/ z' d' q; r' X! Gsds.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)
% ^( c. q' z& `* y7 fsds 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)
! q, R+ d6 J* |# dvoid sdsupdatelen(sds s) ; c, Q% v5 d, V- }
void sdsclear(sds s)
/ P7 J9 g% O1 ]' h& ^2 C/ i$ M1 rsds sdsMakeRoomFor(sds s, size_t addlen) # f/ Z+ k& X: a; V. o7 f' M
sds sdsRemoveFreeSpace(sds s)
7 `3 a# M. G6 {% `* jsize_t sdsAllocSize(sds s)   V; E# i2 D- p8 `) D
void *sdsAllocPtr(sds s)
) M% A7 l7 o2 d: O9 ^- d1 Qvoid sdsIncrLen(sds s, ssize_t incr)
& h8 G6 ?/ H3 _8 u& a- |* Msds sdsgrowzero(sds s, size_t len)
# B+ a" i* p( K) f' lsds sdscatlen(sds s, const void *t, size_t len)
! b9 w0 R! g  S6 b3 @/ N$ Hsds sdscat(sds s, const char *t)
% y, ^- P9 q- W4 Y8 u% ~sds sdscatsds(sds s, const sds t)
' B4 b2 F6 E) F, xsds 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)
7 q: D  _( C4 A8 o5 vint sdsull2str(char *s, unsigned long long v)
" w" j/ t# o' z# @5 v7 osds sdsfromlonglong(long long value)
) x( x$ e! k4 V7 F) [; Q( }sds sdscatvprintf(sds s, const char *fmt, va_list ap)
* \* l! c' W1 E) z0 c5 t4 E* Osds 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)
0 N" y- ]) Y+ y; c) {% ]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)
/ Y2 `* i' E' O- mvoid 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)
9 a) q$ x7 i" I+ p- ~" }; x3 z! Rint 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)
8 j! S5 o6 S, G# I7 p{
/ ]( L. C. i+ U( S5 |2 m    sds mystr = sdsnew("Hello World!");  d8 j# j- k, i7 t  {( @/ k0 k
    printf("%s\n", mystr);
5 F" `, \+ U) b3 x7 y    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)
4 N! J. F6 V2 O3 I9 I% |1 U{: v, u- j& S8 E5 f0 g$ H! c1 }
    char buf[3];
* [2 N$ C0 v; Q    sds mystring;! P  a) L6 N( d6 E6 B) t
( p9 s* ~8 D$ t/ W
    buf[0] = 'A';
$ f* n' o) i! Y- K0 ^    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);
1 [4 j, i7 X- ~, G- Z4 r- y    printf("%s of len %d\n", mystring, (int) sdslen(mystring));* J) Z0 `- V% G* s: j% s
}
8 T% J; @) l" x- m/ x+ H: H+ m  b- f$ R

运行效果:

$ ./sdsdemo
& b5 ]6 P5 X8 {. Q) kABC of len 3
8 m) E/ d* g( J8 L

和 strlen() 有 2 点不同

  • 运行时长固定,sds 内部有数据结构保存着字符串的长度;

  • 长度与字符串内是否有 NULL 无关;


    7 j* c4 U& m) z0 d) `- k* N9 B
2.3 拼接字符串

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 ");
; t5 Z9 [- Z+ o5 v    s = sdscat(s, "World!");
3 L; N+ g: }! U# Z    printf("%s\n", s);( u! l# n5 ^- ~4 B6 p
}
! T4 I5 p& t- {2 [: s: `6 T

运行效果:

$ ./sdsdemo
& q% L/ _$ c/ s4 A, V2 J: C6 @" BHello World!
  q7 ~  ^* Y' f7 r+ v7 c+ ?, H

sdscat 接受的参数是以 NULL 结尾的字符串,如果想摆脱这个限制,可以用 sdscatsds()。

sdscatsds():

int main(void)
% [# B. Y1 j' d0 r) n{
0 `, W' s0 i8 Z- R8 H- p2 B7 U    sds s1 = sdsnew("aaa");
# Z" ?1 f  z3 ?6 G8 b$ C    sds s2 = sdsnew("bbb");
3 e' f: R5 C' W- ]9 X! h# o    s1 = sdscatsds(s1,s2);
2 s2 J$ F: E9 J/ {; N# \$ ^    sdsfree(s2);/ x$ t* b5 ]8 s. J
    printf("%s\n", s1);
7 F3 P9 o5 f. k" N( b9 ~& a}4 T4 R6 g( D& Q3 k

运行效果:

$ ./sdsdemo# p+ a( N: I- [
aaabbb
6 g# Y1 _- O2 Y
2.4 扩展字符串长度

sdsgrowzero():

int main(void)
$ `) O8 D3 e4 f: k( M& i{
8 O, H4 k7 F" {! J9 @- G; J$ m    sds s = sdsnew("Hello");2 p: S6 z% q7 s7 L
    s = sdsgrowzero(s,6);
. y, A% y% E" d. H  t6 X1 [    s[5] = '!'; /* We are sure this is safe*/! u3 ]5 U, r$ d& r& l2 [' W( o
    printf("%s\n", s);
% ^, w4 d3 r* b# n  g8 l' c}
& _$ m! k( P3 ~3 `3 ]% c) D' ]! x3 O

运行效果:

$ ./sdsdemo
  }7 Y% E/ {# ~6 T' xHello!
& }4 E0 H' f) r& ^  a
2.5 格式化字符串

sdscatprintf():

int main(void)
% |6 ?9 Z% v2 U4 a% G- p8 V{" 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);
* _* A, p1 b, c, m}7 Q, R2 @5 [; L- V8 p

运行效果:

$ ./sdsdemo2 s# e4 E5 k7 z. J  {# U/ I7 [9 W' s
The 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");
" `# w* X; ?1 h' }" f! J* D& l    printf("-%s-\n",s);; l9 f3 V! g7 m
}
/ B5 v2 ]9 |, X! P: C

运行效果:

$ ./sdsdemo9 I. [" s- P, d5 Y- e! S
-my string-
1 J: A! F4 q4 M/ ]

去掉了空格和换行符。

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);
3 g- P  ~/ i  g6 z    printf("-%s-\n", s);
0 {- a3 ?) d" ], a}" 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 ^
{
" y* f9 |1 {( _" \    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!");
. m& O' u( n# A6 E    tokens = sdssplitlen(line, sdslen(line)," ",1,&count);
  B3 u' j2 C( L! Y  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
}
; b; O3 S0 v+ I

sdssplitlen() 第 3和4 个参数指定分割符为空格。

运行效果:

$ ./sdsdemo  Z) f9 W- ^" M9 t4 {9 l# V
Hello
( C+ q" R1 l7 z3 V5 LWorld!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
{
$ O* \2 b, x% L8 J2 Y$ ?  n    char *tokens[3] = {"foo","bar","zap"};
) k) W& o0 E  ^& J; X5 F( e; R    sds s = sdsjoin(tokens, 3, "|");8 v* ^9 m& F! i: l# J: c; e
    printf("%s\n", s);
: [5 E0 f1 A# I}* @' H  {! Z- e

运行效果:

$ ./sdsdemo
9 i+ |8 d. |+ y/ p  O% l( }* Pfoo|bar|zap5 E, M' l) W8 p0 c. K) T6 \2 \0 Q

还有其他一些功能,用到再研究吧!

3. 简单了解一下内部实现

在 SDSD 中,使用二进制前缀(头部) 来保存字符串相关的信息,该头部存储在 SDS 返回给用户的字符串的实际指针之前:

+--------+-------------------------------+-----------+
7 J8 K4 x8 J3 _. d| Header | Binary safe C alike string... | Null term |
9 W+ l+ w1 P! o+--------+-------------------------------+-----------+" \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 {
: }1 a) Z- Q3 ]2 N& H* |  \    [...]
. D  t! G: E8 S+ H8 Z; ?; M* P& C5 C3 O    int len;8 l& G, s: K% F! F% e: [8 R
    char buf[];. b# X7 ^# T0 ?+ z- }, q3 c" d$ M
};
0 t( j6 ^8 u, q# F8 j& l8 @6 ]
  • len 存储的是字符串长度;

  • buf 指向紧随其后的字符串首地址;


    & s# y: H; n* u) H; Z4 z

假设你使用的字符串为 "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   \
+ J6 |: u  j* g2 o6 B% F$ b0 l+------------+------------------------+-----------+---------------\! ?: p5 O2 s/ x
             |
8 k  }7 }! E9 ]% t) d; \* E% N             `-> Pointer returned to the user.
" ?! S( o/ c, j3 j

现在,我们来看一下 SDS 分配字符串的大致步骤:

sds sdsnew(const char *init)
0 @6 C8 m1 A* z, ?' g& w) [8 a( g) D    initlen = (init == NULL) ? 0 : strlen(init);8 S0 G) F, W( X7 q, A) a$ U
    sdsnewlen(init, initlen);
+ B* d- c. h# H$ A; q        int hdrlen = sdsHdrSize(type);      // 确定 Header 的长度9 j) t7 u: u8 ?' @/ O
        sh = s_malloc(hdrlen+initlen+1);    // 分配 Header + String + 1 个字节的空间
( b5 e* z/ D) H" H& ]+ B# g* Z6 f% M* i2 ~$ z
        s = (char*)sh+hdrlen;       // 保存 C string 的地址
+ d! b( B' I0 t; a* W        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);
1 _# r' D- S2 L7 y/ t; M        3 X3 l' d. f8 O- h
        s[initlen] = '\0';          // 总是添加一个 NULL
% m5 |5 E7 q3 t        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 章节


, P" w( n9 R, h. G2 J/ A& A
收藏 评论0 发布时间:2020-8-31 23:09

举报

0个回答

所属标签

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版