C語言:優雅的字符串函數庫

2021-02-19 嵌入式ARM

以學習一門語言為例:
大多數人都持有一種觀念,要真正學好一門語言必須得去所學語言當地學習或生活一段時間。

而事實上,大多數人都沒有這樣的學習條件。

解決問題的方法是:
自行改造環境,為自己創造沉浸式的學習環境。

例如:

用新語言寫代碼注釋 / commit message / README / issue;

對了,我作為英文的愛好者,一直想重啟我的英文學習之路,後續想在公眾號裡記錄一些英文相關的知識,請你們不要笑話我~~~

二、字符串函數庫:Simple Dynamic Strings1. 簡介

Simple Dynamic Strings (簡稱 SDS) 是一個 C 語言字符串庫,它增強了 C 語言字符串處理的能力。

設計 SDS 原本是為了滿足設計者自身日常的 C 編程,後來又被轉移到 Redis 中,在 Redis 中被廣泛使用並對其進行了修改以適合於高性能操作。現在,它又被從 Redis 中提取出來的,並 fork 為一個獨立項目。

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

它有什麼優點?

源碼連結:

https://github.com/antirez/sds

源碼文件:

sds.c
sdsalloc.h
sds.h
testhelp.h

相關 API:

sds sdsnewlen(const void *init, size_t initlen) 
sds sdsempty(void) 
sds sdsnew(const char *init) 
sds sdsdup(const sds s) 
void sdsfree(sds s) 
void sdsupdatelen(sds s) 
void sdsclear(sds s) 
sds sdsMakeRoomFor(sds s, size_t addlen) 
sds sdsRemoveFreeSpace(sds s) 
size_t sdsAllocSize(sds s) 
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) 
sds sdscpy(sds s, const char *t) 
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, ...) 
sds sdscatfmt(sds s, char const *fmt, ...) 
sds sdstrim(sds s, const char *cset) 
void sdsrange(sds s, ssize_t start, ssize_t end) 
void sdstolower(sds s) 
void sdstoupper(sds s) 
int sdscmp(const sds s1, const sds s2) 
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) 
int hex_digit_to_int(char c) 
sds *sdssplitargs(const char *line, int *argc) 
sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) 
sds sdsjoin(char **argv, int argc, char *sep) 
sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen)

2. 比較常用的功能2.1 創建字符串

sdsnew() 和 sdsfree():

#include <stdio.h>
#include "sds.h"
#include "sdsalloc.h"

int main(void)
{
    sds mystr = sdsnew("Hello World!");
    printf("%s\n", mystr);
    sdsfree(mystr);
}

運行效果:

$ gcc -o sdsdemo sds.c sdsdemo.c
$ ./sdsdemo
Hello World!

看到了嗎?

printf 直接就可以列印 sds,這就是說 sds 本身就是 C 語言的字符串類型

sds 的定義如下:

typedef char *sds;

也就是說,sds 是能兼容 libc 裡字符串處理函數 (例如strcpy, strcat...)的。

當不再使用 sds 字符串時,就算是空串,也要通過 sdsfree 銷毀字符串。

2.2 獲取字符串長度

sdsnewlen():

int main(void)
{
    char buf[3];
    sds mystring;

    buf[0] = 'A';
    buf[1] = 'B';
    buf[2] = 'C';
    mystring = sdsnewlen(buf,3);
    printf("%s of len %d\n", mystring, (int) sdslen(mystring));
}

運行效果:

$ ./sdsdemo
ABC of len 3

和 strlen() 有 2 點不同

運行時長固定,sds 內部有數據結構保存著字符串的長度;

2.3 拼接字符串

sdscat():

int main(void)
{
    sds s = sdsempty();
    s = sdscat(s, "Hello ");
    s = sdscat(s, "World!");
    printf("%s\n", s);
}

運行效果:

$ ./sdsdemo
Hello World!

sdscat 接受的參數是以 NULL 結尾的字符串,如果想擺脫這個限制,可以用 sdscatsds()。

sdscatsds():

int main(void)
{
    sds s1 = sdsnew("aaa");
    sds s2 = sdsnew("bbb");
    s1 = sdscatsds(s1,s2);
    sdsfree(s2);
    printf("%s\n", s1);
}

運行效果:

$ ./sdsdemo
aaabbb

2.4 擴展字符串長度

sdsgrowzero():

int main(void)
{
    sds s = sdsnew("Hello");
    s = sdsgrowzero(s,6);
    s[5] = '!'; /* We are sure this is safe*/
    printf("%s\n", s);
}

運行效果:

$ ./sdsdemo
Hello!

2.5 格式化字符串

sdscatprintf():

int main(void)
{
    sds s;
    int a = 10, b = 20;
    s = sdsnew("The sum is: ");
    s = sdscatprintf(s,"%d+%d = %d",a,b,a+b);
    printf("%s\n", s);
}

運行效果:

$ ./sdsdemo
The sum is: 10+20 = 30

2.6 截取字符串

sdstrim():去掉指定字符

int main(void)
{
    sds s = sdsnew("         my string\n\n  ");
    sdstrim(s," \n");
    printf("-%s-\n",s);
}

運行效果:

$ ./sdsdemo
-my string-

去掉了空格和換行符。

sdsrange():截取指定範圍內的字符串

int main(void)
{
    sds s = sdsnew("Hello World!");
    sdsrange(s,1,4);
    printf("-%s-\n", s);
}

運行效果:

$ ./sdsdemo
-ello-

2.7 字符串分割 (Tokenization)

sdssplitlen() 和 sdsfreesplitres():

int main(void)
{
    sds *tokens;
    int count, j;

    sds line = sdsnew("Hello World!");
    tokens = sdssplitlen(line, sdslen(line)," ",1,&count);

    for (j = 0; j < count; j++)
        printf("%s\n", tokens[j]);
    sdsfreesplitres(tokens,count);
}

sdssplitlen() 第 3和4 個參數指定分割符為空格。

運行效果:

$ ./sdsdemo
Hello
World!

2.8 字符串合併 (String joining)

sdssplitlen() 和 sdsfreesplitres():

int main(void)
{
    char *tokens[3] = {"foo","bar","zap"};
    sds s = sdsjoin(tokens, 3, "|");
    printf("%s\n", s);
}

運行效果:

$ ./sdsdemo
foo|bar|zap

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

3. 簡單了解一下內部實現

在 SDSD 中,使用二進位前綴(頭部) 來保存字符串相關的信息,該頭部存儲在 SDS 返回給用戶的字符串的實際指針之前:

+---+-+-+
| Header | Binary safe C alike string... | Null term |
+---+-+-+
         |
         `-> Pointer returned to the user.

這個 Header 在代碼中用結構體來描述,該結構體定義大致如下:

struct sdshdr {
    [...]
    int len;
    char buf[];
};

假設你使用的字符串為 "HELLOWORLD",為了提升效率,SDS 可能會提前分配多一些空間,所以實際的內存布局如下:

+--+----+-+\
| len | buf | H E L L O W O R L D \n | Null term |  Free space   \
+--+----+-+\
             |
             `-> Pointer returned to the user.

現在,我們來看一下 SDS 分配字符串的大致步驟:

sds sdsnew(const char *init)
    initlen = (init == NULL) ? 0 : strlen(init);
    sdsnewlen(init, initlen);
        int hdrlen = sdsHdrSize(type);      // 確定 Header 的長度
        sh = s_malloc(hdrlen+initlen+1);    // 分配 Header + String + 1 個字節的空間

        s = (char*)sh+hdrlen;       // 保存 C string 的地址
        SDS_HDR_VAR(8,s);           // 定義 struct sdshdr sh
        sh->len = initlen;          // 初始化 struct sdshdr sh

        if (initlen && init)        // 初始化 C string 
            memcpy(s, init, initlen);
        
        s[initlen] = '\0';          // 總是添加一個 NULL
        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 章節


相關焦點

  • Excel公式技巧53: 使用TEXTJOIN函數反轉文本
    ROW函數組合,可以生成從大到小的連續整數,再將其與MID配合,則可從結尾至開頭逐個取出文本中的字符。在Excel 2016中,Microsoft引入了TEXTJOIN函數,可以方便地連接傳遞給它的參數文本,例如公式:=TEXTJOIN("",TRUE,"e","x","c","e","l","p","e","r","f","e","c","t")得到結果:excelperfect 因此,我們可以使用以前學到MID/LEN/ROW
  • C語言入門經典:必背18個經典程序
    s中刪除存放在c中的字符。replace(char *s,char c1,char c2)實現將s所指向的字符串中所有字符c1用c2替換,字符串、字符c1和c2均在主函數中輸入,將原始字符串和替換後的字符串顯示在屏幕上,並輸出到文件p10_2.out中*/#include<stdio.h>replace(char*s,char c1,char c2){while(*s!
  • Excel教程:四個示例帶你掌握函數replace的用法
    函數REPLACE:將一個字符串中的部分字符用另一個字符串替換。REPLACE(字符串,開始替換的字符位置,要替換的字符長度,替換為)將B列規格中的「×」替換為「*」,效果如D列所示,該怎麼做呢?=REPLACE(A2,1,1,UPPER(LEFT(A2,1)))LEFT(A2,1)部分:用函數left從A2單元格內容左邊截取1個字母,返回"g";UPPER(LEFT(A2,1))部分:將left部分截取的字母"g"用函數upper轉換為大寫"G";公式就是=REPLACE(A2,1,1,"G"),用函數REPLACE從A2單元格內容第1位開始替換,替換的字符長度為1位,替換為"G",實現英文句首字母大寫
  • PyQuery:一個爬蟲界最簡潔優雅的庫
    1.1 將字符串初始化from pyquery import PyQuery #初始化為PyQuery對象doc = PyQuery(html_string)print(type(doc))print(doc)Run<class '
  • Go語言愛好者周刊:第 2 期
    8、go-git: 一個高度可擴展的純 Go 語言實現的 Git[25]當你想使用慣用的 Go API 處理 git 倉庫時,可以考慮使用該庫。9、Liftbridge: 輕量級,容錯的消息流[26]為 NATS[27] 實現持久、多副本的消息日誌的伺服器。10、micro: 一個微服務開發運行時[28]
  • 每日一題:Excel計算一個字符在單元格中出現的次數!
    昨天答疑群裡的朋友問到的這個問題:怎麼計算一個字符在一個單元格中出現了幾次?如下圖,計算A列中指定的字符,在B列字符串中出現的次數。
  • C語言基礎:第一個最簡單程序——Hello World!
    我是先完成的《C語言深處》再寫的《C語言基礎》。很多朋友看過了《C語言深處》後向我反映:直接看這個系列覺得很難,不好理解。並希望我能編寫一個關於C語言編程基礎的系列文章。所以我後來才再寫的《C語言基礎》,這個系列借鑑了很多優秀的C語言教材,比如Stephen Prata的《C Primer Plus》和Brian W.
  • C語言入門:人機猜拳小遊戲的實現
    ①程序能夠接受你的輸入 這個可以通過C語言的輸入語句scanf實現(visualstudio中出於安全考慮要用scanf_s):scanf_s("%d",x);②計算機從1~3中隨機選擇一個整數 如果你閱讀過C語言入門:C語言實現猜數字小遊戲,那你一定知道C語言的輸出隨機數函數srand和rand:srand(time(NULL));
  • 初學者:如何學好C語言?
    《C語言參考手冊》就是《C Reference Manual》,是C語言標準的詳細描述,包括絕大多數C標準庫函數的細節,算得上是最好的標準C語言的工具書。順便提一句,最新的《C程序設計語言》是根據C89標準修訂的,而《C語言參考手冊》描述的是C99標準,二者可能會有些出入,建議按照C99標準學習。
  • VBA進階 | 數組基礎06: 與數組相關的函數——Array函數與IsArray函數
    Array函數語法Array函數返回一個Variant型數組,該數組由傳遞給該函數的參數組成。由Array函數返回的數組只可賦值給一個Variant型變量,不能賦值給已聲明為數組變量的變量。Array函數返回的數組中元素的順序與傳遞給函數的參數值的順序相同。Array函數總是返回Variant類型的數組,但元素的數據類型可以不同,這取決於傳遞給該函數的數值類型。
  • Python:圖片轉字符畫(~情人節神器~)
    字符畫真的很有意思,將圖片中的像素用字符代替,就生成了字符畫。
  • Excel函數公式:SUMIF函數使用技巧範例合集
    條件求和是Excel中應用非常廣泛的,常用函數為SUMIF,可以對數據源範圍內符合指定條件的值求和。
  • Excel函數公式:實用技巧、用名稱計算、給公式添加備註,你確定不來看看
    "Excel函數公式解讀:函數N可以將字符串轉換為0,所以+N("說明性文字")附加到公式中,其結果不變。二、用名稱計算結果。目的:用名稱參與公式的計算。
  • C語言進階技術:同事這些操作把我驚呆了!
    在前期進行軟體調試的時候可能自己會在不同的文件中安插不同測試功能函數,通過這樣方法可以方便的引入和剔除。比如說你需要對源文件中的一些靜態變量進行相關的監控處理,然而又不想在本文件中增加測試代碼,於是便可以在#include"xxx.c"中進行測試函數的編寫來供使用,比如 : 1//FileName :main  2#include <stdio.h> 3#include <stdlib.h> 4 5static int
  • 學習Python的利器:內置函數dir()和help()
    (1)內置函數dir()用來查看對象的成員。
  • C語言也可以搶紅包,速度來圍觀.
    C語言搶紅包源碼+注釋//搶紅包 用了windows API#include<windows.h>#include<stdio.h>#include<string.h>#include<stdlib.h>
  • Excel技巧:VLOOKUP函數如何返回多列
    最近,小編幫朋友解決了一個VLOOKUP函數返回多列的問題,感覺很實用,今天分享給大家。