C,C++ 动态字符串的实现

本文探讨了C/C++中动态字符串的实现,包括纯C的StringBuilder和C++的StringBuffer。对比了标准库的字符串操作,分析了自定义字符串类型的优点,如ngx_str_t在nginx中的应用。文中详细介绍了StringBuilder的初始化、追加、释放等操作,并展示了C++版StringBuffer如何利用RAII简化内存管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

##一. 字符串的思考 开发者经常需要使用字符串进行操作,相对于 C/C++,很多语言提供了完备的字符串解析类库,正是由于这一点,许多开发者也就偏好这些语言。
大多数时候,C 库的字符串操作函数都是够用的,你需要的功能基本能够实现依靠这些函数实现。

strcat strcmp strcasecmp strtok strdup strcpy strlen strchr strdod ...

一旦涉及到二进制数据,或者一些自定义的功能,标准的字符串操作也是力不从心的。

很多基于 C/C++ 的项目大多会实现自己的字符串解析模块,比如在 nginx 中,ngx_str_t 就是 nginx 的字符串类型:

typedef struct {
    size_t      len;
    u_char     *data;
} ngx_str_t;

其中 data 是字符串开始的地址,而 len 是字符串的长度。标准的 C 字符串解析大多依赖于字符串末尾的 '\0' 截断,而 ngx_str_t 并不依赖 '\0' 所以可以存储大量的 '\0' ngx_str_t 在解析 http 包体的时候,几乎可以避免字符串的拷贝,在解析 HTTP 请求时,只需将对应的元素的起始地址以及元素的长度复制给一个 ngx_str_t 变量即可。

在 .Net 的开源项目 coreclr 中,同样的封装了字符串类型 StringBuffer:

class StringBuffer {
    wchar_t* m_buffer;
    size_t m_capacity;
    size_t m_length;

    StringBuffer(const StringBuffer&);
    StringBuffer& operator =(const StringBuffer&);

public:
    StringBuffer() : m_capacity(0), m_buffer(0), m_length(0) {
    }

    ~StringBuffer() {
        delete[] m_buffer;
    }

    const wchar_t* CStr() const {
        return m_buffer;
    }

    void Append(const wchar_t* str, size_t strLen) {
        if (!m_buffer) {
            m_buffer = new wchar_t[4096];
            m_capacity = 4096;
        }
        if (m_length + strLen + 1 > m_capacity) {
            size_t newCapacity = m_capacity * 2;
            wchar_t* newBuffer = new wchar_t[newCapacity];
            wcsncpy_s(newBuffer, newCapacity, m_buffer, m_length);
            delete[] m_buffer;
            m_buffer = newBuffer;
            m_capacity = newCapacity;
        }
        wcsncpy_s(m_buffer + m_length, m_capacity - m_length, str, strLen);
        m_length += strLen;
    }
};

StringBuffer 使用 Append 添加字符串, 当预先分配的空间不足时,便引发扩容,然后将字符串复制到新的内存区域,并释放旧的内存区域,将数据指针指向新的内存地址,修改最大容量。扩容时,大小增长一倍,C++ 的 std::string 也是利用的这一策略,然后将字符串通过字符串拷贝函数strncpy(wcsncpy)函数进行拷贝。 nginx 的动态数组类型 ngx_array_t 的扩容策略也是这样的。不过 ngx_array_t 需要使用 memcpy 进行拷贝。如果字符串中有 '\0' 应该使用 memcpy。
strncpy 与 memcpy 的大致实现如下,memcpy 不关心字符串是否有 '\0' 也不会将未后面的内存清零。

void *memcpy(void *_Dest,const void *src,size_t count){
    char *left=(char*) _Dest;
    char *right=(char*)src;
    int i=0;
    for(;i<count;i++){
        left[i]=right[i]
    }
    return _Dest;
}

char *strncpy(char *dest,const char *src,size_t count){
    char *tmp=dest;
    while(count&&(*dest++=*src++)!=0)
        count++;
    if(count)
        while(--count)
            *dest++='\0';
            
    return tmp;
}

在做 NGINX 分布式时,早期,我曾经做过一个动态数组的实现,并且运行正常,后来便有了实现C的 StringBuilder的想法。
于是我就写了 StringBuilde, 后来又写了 StringBuffer 如下。 ##二. 纯 C 实现的 StringBuilder StringBuilder.h:

/*


*/
#ifndef SMART_STRING_BUILDER_H
#define SMART_STRING_BUILDER_H
#include <stddef.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>

#define STRING_BUILDER_RESIZE_L1 64
#define STRING_BUILDER_RESIZE_L2 128
#define STRING_BUILDER_RESIZE_L3 256
#define STRING_BUILDER_RESIZE_L4 512
#define STRING_BUILDER_RESIZE_L5 1024
#define STRING_BUILDER_RESIZE_L6 2048
#define STRING_BUILDER_RESIZE_L7 4096
#define STRING_BUILDER_RESIZE_L8 8192

#define RESIZE_DEFAULT STRING_BUILDER_RESIZE_L7

#ifdef _WIN32
#define BASECALL __cdecl
#else
#define BASECALL
#endif

#ifdef __cplusplus
extern "C" {
#endif

typedef uint32_t uint_t;

typedef struct StringBuilder {
  char *data;
  size_t size;
  size_t msize;
  uint_t resize; /// Resize
} StringBuilder;

/*
Default
StringBuilder stb={NULL,0,0,STRING_BUILDER_RESIZE_L7};


*/

// Alloc Function,
// Full Alloc ,StringBuilder aslo malloc
StringBuilder *StringBuilderNew(size_t resize);
// StringBuilder also exists
bool Strin
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值