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

C语言——字符串函数库

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

以学习一门语言为例:
, Q1 Z. u& p  X8 m. T! L. ?2 ]大多数人都持有一种观念,要真正学好一门语言必须得去所学语言当地学习或生活一段时间。

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

解决问题的方法是:- j; `( h9 W8 C# F& S9 U4 e" I9 y
自行改造环境,为自己创造沉浸式的学习环境。

例如:

  • 看新语言的电影;

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

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

  • 用新语言写 todo list;

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

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

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

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

  • ...


    " U3 C$ Q. q, m3 [

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


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

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

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

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

它有什么优点?

  • 使用更简单;

  • 二进制安全;

  • 效率更高;

  • 与 C 字符串函数兼容;

    # I$ ]. l$ N4 ^5 w

源码链接:

http://github.com/antirez/sds
$ n' q2 D  S+ b3 }" V: s; J

源码文件:

sds.c# L3 y4 M# x" \; ?0 ~6 p
sdsalloc.h# B% q7 |2 X+ H( c" ]7 a# L
sds.h) Q, Q; ^! g1 I& w6 A5 p
testhelp.h
( f  z" m. H$ m0 q# v$ S! [) J8 q

相关 API:

sds sdsnewlen(const void *init, size_t initlen)
+ _  @, b. G( ]sds sdsempty(void) 5 u) U2 ]6 _+ q0 [1 s
sds sdsnew(const char *init)
2 L6 {8 O2 u! t/ }9 J5 Dsds sdsdup(const sds s)
4 V/ ^& {- i/ t" Gvoid sdsfree(sds s)
1 V1 l# x1 N  V( [" Vvoid sdsupdatelen(sds s)
. C, _) A- ^) \, L$ J5 lvoid 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)
4 X' m. l! j& o* U3 ~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)
* o9 m  ^8 L+ s& G. ssds sdsgrowzero(sds s, size_t len)
! Y( M) n1 L2 Nsds sdscatlen(sds s, const void *t, size_t len) 1 }- ?$ v+ ~3 W# p; E; L
sds sdscat(sds s, const char *t)
, y( n! P( ^" |' h( Esds 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)
' |/ e/ u) M+ ^: T9 q' Uint 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)
1 N- n5 t2 H9 b2 U5 E3 T9 ]sds sdscatprintf(sds s, const char *fmt, ...)
9 e; s2 ~0 k, w9 k+ W" r$ esds sdscatfmt(sds s, char const *fmt, ...)
! v8 B7 _, D, T/ z9 y% _) Gsds 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)
' V" C& A" j2 I' V/ V: ~/ xint 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)
" M& T+ F) b0 F2 g% @, Yvoid sdsfreesplitres(sds *tokens, int count)
$ Y. }; {# j9 f5 y* bsds sdscatrepr(sds s, const char *p, size_t len)
" T9 S3 B3 R3 f- z1 j1 T" n; S* {4 gint is_hex_digit(char c) 8 A4 M+ b. A5 k1 z7 f% ]) S
int hex_digit_to_int(char c)
$ N) n# u) s8 @* isds *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)
4 m6 x7 Q2 L4 Z2 K1 Q  v1 L) |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>
* f( n2 E2 x& i9 f#include "sds.h") c! V" K$ ?$ r/ Q- N' r$ G; O
#include "sdsalloc.h"
* F- `0 @4 s; X$ f
/ _& u8 W  U% mint main(void)
9 ^- ~: ]) L2 F' R0 r{- z: ^* Z% L2 A7 N3 ~
    sds mystr = sdsnew("Hello World!");
6 S/ N6 C! q% d# G  d+ X    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
0 P2 w6 l0 ^" G/ \$ ./sdsdemo
5 u. v# B6 g8 n9 oHello World!* E- H* y* s# c/ b

看到了吗?

printf 直接就可以打印 sds,这就是说 sds 本身就是 C 语言的字符串类型。

sds 的定义如下:

typedef char *sds;
2 {, n8 f3 [3 h, e

也就是说,sds 是能兼容 libc 里字符串处理函数 (例如strcpy, strcat...)的。

当不再使用 sds 字符串时,就算是空串,也要通过 sdsfree 销毁字符串。

2.2 获取字符串长度

sdsnewlen():

int main(void)
! W6 M) a0 @8 N: `$ g' E2 F& T{
- k: z% h/ v5 P" d1 z$ z    char buf[3];. y4 x( P2 O: [7 f/ j
    sds mystring;
" ^4 c7 [& k$ g6 h2 I# {8 ]6 o- E# {7 h2 U; x
    buf[0] = 'A';
! [( \0 t( ]8 {) ^1 t    buf[1] = 'B';
0 Y1 k: J$ I5 `+ y$ g- _    buf[2] = 'C';* a4 O' B2 e  W1 R& P% k
    mystring = sdsnewlen(buf,3);
# O. I+ L5 y' ~* S+ T; K    printf("%s of len %d\n", mystring, (int) sdslen(mystring));
# P' M( h2 n! e$ V( a! H3 Q6 l; C}
. M2 M* [1 c" D+ a1 ]4 J5 v) B

运行效果:

$ ./sdsdemo: ~9 `$ H$ d- s5 k4 ?0 P
ABC of len 38 a/ d" B8 a( v+ g' V

和 strlen() 有 2 点不同:

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

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

    $ t. k3 c( g% B4 ~+ M7 ~- ?
2.3 拼接字符串

sdscat():

int main(void)
6 ]! ^* m# k, z- q3 E{
' z1 f7 k& O' |# f    sds s = sdsempty();
' {1 M' ~- R/ Q    s = sdscat(s, "Hello ");6 H$ _& E5 V+ U8 g7 \4 |0 E
    s = sdscat(s, "World!");
, T6 ]3 U- y, V; e7 B3 Y- o    printf("%s\n", s);
8 e$ f7 N1 j9 T& ?& [" y0 U+ f% ~& ?}( e6 a1 g2 w9 t% p1 `+ y

运行效果:

$ ./sdsdemo* N' s; v# n$ G" T+ Y8 W0 r
Hello World!
) R9 ?8 ~5 {4 @) v4 i/ W' q0 C

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

sdscatsds():

int main(void)
; S& g+ h' U; R{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");
) |! P; u9 o0 R; b0 Q    s1 = sdscatsds(s1,s2);
8 K: U& j9 q- D4 Y) L- j0 t( o    sdsfree(s2);
) V' t4 J# @: S+ K    printf("%s\n", s1);
+ s+ M; R7 ~% @9 ~3 e" Z% v) _}
2 J& V6 j% M  l& `. U+ F; i

运行效果:

$ ./sdsdemo
# w0 s7 X9 T2 |. M5 ?# {aaabbb
1 s  l5 t8 N( q+ n" S9 d3 E. m
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*/
5 d9 Q, F8 Z% f( E2 ^6 O/ F    printf("%s\n", s);% y- V9 X' O2 H  y. o- w
}: |' N+ |2 i5 y. ]) B

运行效果:

$ ./sdsdemo
6 c: ~8 |. v! M7 V( pHello!
1 `' G$ q3 ^# Y0 |+ |  E& I5 x
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;
* }% @8 o. F* I8 h    s = sdsnew("The sum is: ");
; ]! e9 ?7 L$ A; N6 P* p: ^) r    s = sdscatprintf(s,"%d+%d = %d",a,b,a+b);
2 \* o! z8 R& t' G    printf("%s\n", s);
6 z7 i+ @, ^$ W2 L3 \1 s+ Q! B}: k1 W/ z, G  q  r) Z; t; _$ N

运行效果:

$ ./sdsdemo
3 Q) l& y2 n9 bThe sum is: 10+20 = 30
( [* X4 O, i! v; U# o
2.6 截取字符串

sdstrim():去掉指定字符

int main(void)
1 |3 a( f" H5 h* @, b$ Q{" \$ x' G# B5 A% ~
    sds s = sdsnew("         my string\n\n  ");
8 l! ?' i8 z, ^8 I1 ?    sdstrim(s," \n");
$ V; g- T* d% q! w6 Z! k9 D    printf("-%s-\n",s);
- T5 @3 S2 j% d9 b2 B$ ^3 i}
1 c! j7 X6 a! O1 [! l

运行效果:

$ ./sdsdemo
! w! E0 a( ^0 C-my string-
! A1 }- v& m  a, Y' s

去掉了空格和换行符。

sdsrange():截取指定范围内的字符串

int main(void)
( s- N' T7 S) S1 T2 ?$ u  a{  p1 ?3 z9 ]) X
    sds s = sdsnew("Hello World!");
! G' B1 _, S8 Y. r: A% G; n    sdsrange(s,1,4);
! C- z9 b2 w: Q( Z    printf("-%s-\n", s);
, O; t( k5 g+ C4 N}
( W* f) ^' @! z% p; z

运行效果:

$ ./sdsdemo
+ Z& {9 j7 P' W" o, d& _1 U-ello-
" x' Y( v" I: ]5 c) @; Z
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;
7 X2 }7 r$ M1 C7 {1 X1 M/ @2 u8 O& a3 t
    sds line = sdsnew("Hello World!");
# h4 }9 [9 `, B+ F# j    tokens = sdssplitlen(line, sdslen(line)," ",1,&count);
/ E# w; {% t  N" O. @& T6 y7 V, c) w% O* k) s; x5 a& R
    for (j = 0; j < count; j++)
9 u9 Q& i9 A1 t' [, ?        printf("%s\n", tokens[j]);
/ s( g; z2 H. l    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 W
Hello7 h- y. E* g/ S) B
World!
5 G' n8 T. F( U
2.8 字符串合并 (String joining)

sdssplitlen() 和 sdsfreesplitres():

int main(void)9 l4 W. X$ H( v. M8 r& [
{
5 o5 K2 \) E  A* n    char *tokens[3] = {"foo","bar","zap"};; m7 D1 `* O: m
    sds s = sdsjoin(tokens, 3, "|");
" p8 l3 g/ @% H; X; ?- L- a6 e3 I, t    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" r
foo|bar|zap
. j: z! }; Q' l! X' O2 Y. _* h

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

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

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

+--------+-------------------------------+-----------+
: r: Q7 H  O5 Z% g6 ?8 I7 ~| Header | Binary safe C alike string... | Null term |, ~1 A" i- z$ G5 D! J7 F& z+ K
+--------+-------------------------------+-----------+
5 T3 t; Z, X( r: `7 y1 Q# ?         |
. S3 E9 O2 U: V. Y8 Q8 `3 \9 |         `-> Pointer returned to the user.
* K* J! F. j" ^2 F

这个 Header 在代码中用结构体来描述,该结构体定义大致如下:

struct sdshdr {
, V+ d1 o$ _# ^4 {- q( \    [...]
" o4 A2 s* f$ j! t8 S8 |9 L    int len;
6 u. q- O+ v) P8 D0 ]    char buf[];( k) f5 ?' x- g. |8 N; k
};
2 o% v2 P, ?0 b, I5 Z& |7 Y
  • len 存储的是字符串长度;

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


    4 \! n" m4 R& _/ j  N

假设你使用的字符串为 "HELLOWORLD",为了提升效率,SDS 可能会提前分配多一些空间,所以实际的内存布局如下:

+------------+------------------------+-----------+---------------\
7 s$ h5 e" n7 x4 Y5 I2 V+ Y0 U| 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
             |
2 D& H( p+ e& X3 v             `-> Pointer returned to the user.7 e% Z6 q: Z8 A) q# ?

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

sds sdsnew(const char *init)
- v' S, r3 L2 @# R+ k& Q7 P    initlen = (init == NULL) ? 0 : strlen(init);
4 S: \1 O8 Y5 @! P1 @0 x    sdsnewlen(init, initlen);
5 x* Q9 h. p: E+ q$ {        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 ~

! E4 @2 \0 `+ v( c- g& @        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
2 ~: r# j1 r) i% M) v3 {% f
3 _3 m; d) Z% S; _2 w) \+ o        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
  F7 @$ ]: q: C3 D$ H

其他的 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
赞 收藏 评论0 发布时间:2020-8-31 23:09

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版