众所周知,C++ 中的string使用比较方便。关于C++ 中的string源码实现,可以参考这篇文章:源码分析C++的string的实现
最近工作中使用C语言,但又苦于没有高效的字符串实现,字符串的拼接和裁剪都比较麻烦,而且每个字符串都需要申请内存,内存的申请和释放也很容易出bug,怎么高效的实现一个不需要处理内存问题并且可以动态扩容进行拼接和裁剪的string呢?
一个好的string应该有以下功能?
- 创建字符串
- 删除字符串
- 尾部追加字符串
- 头部插入字符串
- 从尾部删除N个字符
- 从头部删除N个字符
- 裁剪字符串
- 获取字符串长度
- 获取完整字符串
下面,我们来看看各个功能的实现。
首先定义一个string的句柄,相当于C++中的实例。
struct c_string; | |
typedef struct c_string c_string_t; |
在内部string的实现如下:
// string的初始内存大小 | |
static const size_t c_string_min_size =; | |
struct c_string { | |
char *str; // 字符串指针 | |
size_t alloced; // 已分配的内存大小 | |
size_t len; // 字符串的实际长度 | |
}; |
创建字符串:
c_string_t *c_string_create(void) { | |
c_string_t *cs; | |
cs = calloc(, sizeof(*cs)); | |
cs->str = malloc(c_string_min_size); | |
*cs->str = '\'; | |
// 初始分配内存大小是,之后每次以2倍大小扩容 | |
cs->alloced = c_string_min_size; | |
cs->len =; | |
return cs; | |
} |
销毁字符串:
void c_string_destroy(c_string_t *cs) { | |
if (cs == NULL) return; | |
free(cs->str); | |
free(cs); | |
} |
内部如何扩容呢:
static void c_string_ensure_space(c_string_t *cs, size_t add_len) { | |
if (cs == NULL || add_len ==) return; | |
if (cs->alloced >= cs->len + add_len +) return; | |
while (cs->alloced < cs->len + add_len +) { | |
cs->alloced <<=; // 每次以2倍大小扩容 | |
if (cs->alloced ==) { | |
// 左移到最后可能会变为,由于alloced是无符号型,减一则会变成UINT_MAX | |
cs->alloced--; | |
} | |
} | |
cs->str = realloc(cs->str, cs->alloced); | |
} |
在尾部追加字符串:
void c_string_append_str(c_string_t *cs, const char *str, size_t len) { | |
if (cs == NULL || str == NULL || *str == '\') return; | |
if (len ==) len = strlen(str); | |
c_string_ensure_space(cs, len); // 确保内部有足够的空间存储字符串 | |
memmove(cs->str + cs->len, str, len); | |
cs->len += len; | |
cs->str[cs->len] = '\'; | |
} |
在尾部追加字符:
void c_string_append_char(c_string_t *cs, char c) { | |
if (cs == NULL) return; | |
c_string_ensure_space(cs,); | |
cs->str[cs->len] = c; | |
cs->len++; | |
cs->str[cs->len] = '\'; | |
} |
在尾部追加整数:
void c_string_append_int(c_string_t *cs, int val) { | |
char str[]; | |
if (cs == NULL) return; | |
snprintf(str, sizeof(str), "%d", val); // 整数转为字符串 | |
c_string_append_str(cs, str,); | |
} |
在头部插入字符串:
void c_string_front_str(c_string_t *cs, const char *str, size_t len) { | |
if (cs == NULL || str == NULL || *str == '\') return; | |
if (len ==) len = strlen(str); | |
c_string_ensure_space(cs, len); | |
memmove(cs->str + len, cs->str, cs->len); | |
memmove(cs->str, str, len); | |
cs->len += len; | |
cs->str[cs->len] = '\'; | |
} |
在头部插入字符:
void c_string_front_char(c_string_t *cs, char c) { | |
if (cs == NULL) return; | |
c_string_ensure_space(cs,); | |
memmove(cs->str +, cs->str, cs->len); | |
cs->str[] = c; | |
cs->len++; | |
cs->str[cs->len] = '\'; | |
} |
在头部插入整数:
void c_string_front_int(c_string_t *cs, int val) { | |
char str[]; | |
if (cs == NULL) return; | |
snprintf(str, sizeof(str), "%d", val); | |
c_string_front_str(cs, str,); | |
} |
清空字符串:
void c_string_clear(c_string_t *cs) { | |
if (cs == NULL) return; | |
c_string_truncate(cs,); | |
} |
裁剪字符串:
void c_string_truncate(c_string_t *cs, size_t len) { | |
if (cs == NULL || len >= cs->len) return; | |
cs->len = len; | |
cs->str[cs->len] = '\'; | |
} |
删除头部的N个字符:
void c_string_drop_begin(c_string_t *cs, size_t len) { | |
if (cs == NULL || len ==) return; | |
if (len >= cs->len) { | |
c_string_clear(cs); | |
return; | |
} | |
cs->len -= len; | |
memmove(cs->str, cs->str + len, cs->len +); | |
} |
删除尾部的N个字符:
void c_string_drop_end(c_string_t *cs, size_t len) { | |
if (cs == NULL || len ==) return; | |
if (len >= cs->len) { | |
c_string_clear(cs); | |
return; | |
} | |
cs->len -= len; | |
cs->str[cs->len] = '\'; | |
} |
获取字符串的长度:
size_t c_string_len(const c_string_t *cs) { | |
if (cs == NULL) return; | |
return cs->len; | |
} |
返回字符串指针,使用的是内部的内存:
const char *c_string_peek(const c_string_t *cs) { | |
if (cs == NULL) return NULL; | |
return cs->str; | |
} |
重新分配一块内存存储字符串返回:
char *c_string_dump(const c_string_t *cs, size_t *len) { | |
char *out; | |
if (cs == NULL) return NULL; | |
if (len != NULL) *len = cs->len; | |
out = malloc(cs->len +); | |
memcpy(out, cs->str, cs->len +); | |
return out; | |
} |
测试代码如下:
int main() { | |
c_string_t *cs = c_string_create(); | |
c_string_append_str(cs, "", 0); | |
c_string_append_char(cs, ''); | |
c_string_append_int(cs,); | |
printf("%s \n", c_string_peek(cs)); | |
c_string_front_str(cs, "", 0); | |
printf("%s \n", c_string_peek(cs)); | |
c_string_drop_begin(cs,); | |
printf("%s \n", c_string_peek(cs)); | |
c_string_drop_end(cs,); | |
printf("%s \n", c_string_peek(cs)); | |
c_string_destroy(cs); | |
return; | |
} |
输出:
12345
78912345
912345
9123
完整代码如下:头文件:
struct c_string; | |
typedef struct c_string c_string_t; | |
c_string_t *c_string_create(void); | |
void c_string_destroy(c_string_t *cs); | |
void c_string_append_str(c_string_t *cs, const char *str, size_t len); | |
void c_string_append_char(c_string_t *cs, char c); | |
void c_string_append_int(c_string_t *cs, int val); | |
void c_string_front_str(c_string_t *cs, const char *str, size_t len); | |
void c_string_front_char(c_string_t *cs, char c); | |
void c_string_front_int(c_string_t *cs, int val); | |
void c_string_clear(c_string_t *cs); | |
void c_string_truncate(c_string_t *cs, size_t len); | |
void c_string_drop_begin(c_string_t *cs, size_t len); | |
void c_string_drop_end(c_string_t *cs, size_t len); | |
size_t c_string_len(const c_string_t *cs); | |
const char *c_string_peek(const c_string_t *cs); | |
char *c_string_dump(const c_string_t *cs, size_t *len); |
源文件:
#include <ctype.h> | |
#include <stdbool.h> | |
#include <stdlib.h> | |
#include <stdio.h> | |
#include <string.h> | |
static const size_t c_string_min_size =; | |
struct c_string { | |
char *str; | |
size_t alloced; | |
size_t len; | |
}; | |
c_string_t *c_string_create(void) { | |
c_string_t *cs; | |
cs = calloc(, sizeof(*cs)); | |
cs->str = malloc(c_string_min_size); | |
*cs->str = '\'; | |
cs->alloced = c_string_min_size; | |
cs->len =; | |
return cs; | |
} | |
void c_string_destroy(c_string_t *cs) { | |
if (cs == NULL) return; | |
free(cs->str); | |
free(cs); | |
} | |
static void c_string_ensure_space(c_string_t *cs, size_t add_len) { | |
if (cs == NULL || add_len ==) return; | |
if (cs->alloced >= cs->len + add_len +) return; | |
while (cs->alloced < cs->len + add_len +) { | |
cs->alloced <<=; | |
if (cs->alloced ==) { | |
cs->alloced--; | |
} | |
} | |
cs->str = realloc(cs->str, cs->alloced); | |
} | |
void c_string_append_str(c_string_t *cs, const char *str, size_t len) { | |
if (cs == NULL || str == NULL || *str == '\') return; | |
if (len ==) len = strlen(str); | |
c_string_ensure_space(cs, len); | |
memmove(cs->str + cs->len, str, len); | |
cs->len += len; | |
cs->str[cs->len] = '\'; | |
} | |
void c_string_append_char(c_string_t *cs, char c) { | |
if (cs == NULL) return; | |
c_string_ensure_space(cs,); | |
cs->str[cs->len] = c; | |
cs->len++; | |
cs->str[cs->len] = '\'; | |
} | |
void c_string_append_int(c_string_t *cs, int val) { | |
char str[]; | |
if (cs == NULL) return; | |
snprintf(str, sizeof(str), "%d", val); | |
c_string_append_str(cs, str,); | |
} | |
void c_string_front_str(c_string_t *cs, const char *str, size_t len) { | |
if (cs == NULL || str == NULL || *str == '\') return; | |
if (len ==) len = strlen(str); | |
c_string_ensure_space(cs, len); | |
memmove(cs->str + len, cs->str, cs->len); | |
memmove(cs->str, str, len); | |
cs->len += len; | |
cs->str[cs->len] = '\'; | |
} | |
void c_string_front_char(c_string_t *cs, char c) { | |
if (cs == NULL) return; | |
c_string_ensure_space(cs,); | |
memmove(cs->str +, cs->str, cs->len); | |
cs->str[] = c; | |
cs->len++; | |
cs->str[cs->len] = '\'; | |
} | |
void c_string_front_int(c_string_t *cs, int val) { | |
char str[]; | |
if (cs == NULL) return; | |
snprintf(str, sizeof(str), "%d", val); | |
c_string_front_str(cs, str,); | |
} | |
void c_string_clear(c_string_t *cs) { | |
if (cs == NULL) return; | |
c_string_truncate(cs,); | |
} | |
void c_string_truncate(c_string_t *cs, size_t len) { | |
if (cs == NULL || len >= cs->len) return; | |
cs->len = len; | |
cs->str[cs->len] = '\'; | |
} | |
void c_string_drop_begin(c_string_t *cs, size_t len) { | |
if (cs == NULL || len ==) return; | |
if (len >= cs->len) { | |
c_string_clear(cs); | |
return; | |
} | |
cs->len -= len; | |
/* + to move the NULL. */ | |
memmove(cs->str, cs->str + len, cs->len +); | |
} | |
void c_string_drop_end(c_string_t *cs, size_t len) { | |
if (cs == NULL || len ==) return; | |
if (len >= cs->len) { | |
c_string_clear(cs); | |
return; | |
} | |
cs->len -= len; | |
cs->str[cs->len] = '\'; | |
} | |
size_t c_string_len(const c_string_t *cs) { | |
if (cs == NULL) return; | |
return cs->len; | |
} | |
const char *c_string_peek(const c_string_t *cs) { | |
if (cs == NULL) return NULL; | |
return cs->str; | |
} | |
char *c_string_dump(const c_string_t *cs, size_t *len) { | |
char *out; | |
if (cs == NULL) return NULL; | |
if (len != NULL) *len = cs->len; | |
out = malloc(cs->len +); | |
memcpy(out, cs->str, cs->len +); | |
return out; | |
} |