前段时间,在工作中需要使用Sqlite3嵌入式数据库,在生成sql语句的过程中,每次都要去预估sql大概的长度,有些时候插入一条记录的时候,sql语句可能会很长,如果是在栈上分配,觉得不是很妥,索性就写了一个可以自动扩容的string buffer,用于存储生成的sql。
1. 首先是声明string buffer:
typedef struct _string_buffer_t { uint32_t size; uint32_t len; char* str; uint16_t free:1; // self free or not uint16_t reverse:15; }string_buffer_t;
2. 接下来是实现string buffer对象的初始化与销毁的方法:
int string_buffer_init(string_buffer_t *self, char *p, uint32_t size) { if(self == NULL){ ERR("null point"); return ENULL_POINT; } if(size == 0){ size = STRING_BUFFER_DEFAULT_SIZE; } if(p){ self->str = p; self->free = 0; }else{ self->str = (char *)MALLOC(size); if(self->str == NULL){ ERR("MALLOC failed"); return EMALLOC; } self->free = 1; } self->size = size; self->len = 0; return RET_SUCCESS; } void string_buffer_reset(string_buffer_t *self) { if(self){ if(self->str && self->free){ free(self->str); memset(self, 0, sizeof(*self)); } } }
在init方法中,允许用户使用外部分配的内存,如果不使用,则p=NULL.
3. 接着实现string expand方法:
static int _string_buffer_expand(string_buffer_t *self, size_t len) { if(self->free == 0){ DBG("can not expand"); return EINVAL_ARG; } size_t t_len = self->len + len + 1; if(t_len <= self->size) return RET_SUCCESS; t_len = self->size + len + 1; t_len = ((t_len + STRING_BUFFER_MASK) && ~STRING_BUFFER_MASK); char *ptr = (char *)realloc(self->str, t_len); if(ptr != NULL){ self->size = t_len; if(ptr != self->str){ memcpy(ptr, self->str, self->len); self->str = ptr; } return RET_SUCCESS; }else{ ERR("realloc failed"); return EMALLOC; } }
4. 最后,为了方便,实现比较常用的几个方法:
int string_buffer_snprintf(string_buffer_t *self, const char *fmt, ...) { int ret; va_list ap; va_start(ap, fmt); if(self->len >= self->size){ if((ret = _string_buffer_expand(self, self->len)) < 0){ return ret; } } _RETRY: ret = vsnprintf(self->str + self->len, self->size - self->len, fmt, ap); if(ret <= 0){ DBG("vsnprintf failed"); return -1; } if((self->len + ret) >= self->size){ if(self->str[self->size-1] != '\0'){ if((ret = _string_buffer_expand(self, ret)) < 0){ return ret; } goto _RETRY; } } self->len += ret; self->str[self->len] = '\0'; va_end(ap); return RET_SUCCESS; } int string_buffer_strcat(string_buffer_t *self, const char *str) { size_t len = strlen(str); int ret = _string_buffer_expand(self, len); if(ret != RET_SUCCESS) return ret; strcpy(self->str + self->len, str); self->len += len; self->str[self->len] = '\0'; return RET_SUCCESS; } int string_buffer_insert(string_buffer_t *self, int index, const char *str) { size_t len = strlen(str); size_t start; int ret; if(index > self->len){ DBG("overlen: index(%d) > len(%u)", index, self->len); return EINVAL_ARG; } if(index >= 0){ start = index; }else{ if(index + self->len >= 0){ start = index + self->len; }else{ DBG("overlen: index(%d) < -len(%u)", index, self->len); return EINVAL_ARG; } } size_t end = start + len; if(end >= self->size){ if((ret = _string_buffer_expand(self, len+1)) < 0){ return ret; } } strcpy(self->str+start, str); self->len = end; self->str[self->len] = '\0'; return RET_SUCCESS; } void string_buffer_debug(string_buffer_t *self) { if(self && self->str){ printf("str: %s\n", self->str); } } const char *string_buffer_get_str(string_buffer_t *self) { if(self) return self->str; }
其中string_buffer_snprintf就像是snprintf,可以直接格式化字符串。string_buffer_strcat跟strcat一样效果,将字符串str连接到原先字符串的后面。string_buffer_insert方法,用于在指定的位置index处,插入字符串str,这里的index可以是负数,如-1表示插入到最后一个字符的位置处。string_buffer_debug方法是为了方便调试,将里面的字符串打印出来。
5. 最后这里简单的写了一个测试程序
int string_buffer_test(void) { string_buffer_t sql_buf; int ret; printf("** test sql_buffer:\n"); ret = sql_buffer_init(&sql_buf, NULL, 0); ASSERT(ret == RET_SUCCESS); ret = string_buffer_strcat(&sql_buf, \ "CREATE TABLE test_table(id INT PRIMARY KEY,name CHAR NOT NULL);"); ASSERT(ret == RET_SUCCESS); string_buffer_debug(&sql_buf); ret = string_buffer_snprintf(&sql_buf, "INSERT INTO %s(id, name) VALUES(%d,'%s')", "test_table", 1, "xxxx"); ASSERT(ret == RET_SUCCESS); string_buffer_debug(&sql_buf); ret = string_buffer_snprintf(&sql_buf, "UPDATE %s SET name='%s' WHERE id=%d,",\ "test_table", "yyyy", 1); ASSERT(ret == RET_SUCCESS); ret = string_buffer_insert(&sql_buf, -1, ""); string_buffer_debug(&sql_buf); sql_buffer_reset(&sql_buf); printf("** test sql_buffer success\n"); return 0; } int main(int argc, char **argv) { return string_buffer_test(); }