关于设计一个 C 语言的动态扩容缓冲区

日月不肯迟,四时相催迫。

这个项目的条条例例都是小组的学姐学长的写出来的呐(不得不感叹小组学长的智慧吖),我也只是按照要求写了每个API,写的磕磕盼盼,但是呢,学到了不少知识,现在分享个大家。

先来了解了解做这个项目需要掌握什么知识叭

知识点

  1. 字符串。
  2. 面向对象的 C 语言设计。
  3. 动态内存分配。
  4. Linux File API。
  5. getline。 

一.首先看缓冲区类的定义

struct strbuf {
  int len;     //当前缓冲区(字符串)长度
  int alloc;   //当前缓冲区(字符串)容量
  char *buf;   //缓冲区(字符串)
}; 
  • strbuf 的成员 len 代表的是 buf 缓冲区的长度,每次我们将字符串追加入 strbuf 中,我们都应该使用 strbuf_setlen() 去更新 strbuf 的长度 len,注意 123\0456 的长度不是 3,而是 7。
  • strbuf 的成员 alloc 代表的是 buf 缓冲区的容量,也就是我们每次动态分配的数组大小,每当我们需要向 sb 内追加一个字符串,我们需要计算当前的字符串长度加上追加的字符串长度,如果超过了当前的容量,我们就需要把容量扩大一倍,然后将字符串添加进去。

二.接下来直接看任务

Part 2A

TASK

实现字符串缓冲区类 strbuf 简单的初始化,填充,释放,交换,比较,清空等操作。

API 功能简介
void strbuf_init(struct strbuf *sb, size_t alloc); 初始化 sb 结构体,容量为 alloc
void strbuf_attach(struct strbuf *sb, void *str, size_t len, size_t alloc); 将字符串填充到 sb 中,长度为 len, 容量为 alloc
void strbuf_release(struct strbuf *sb); 释放 sb 结构体的内存。
void strbuf_swap(struct strbuf *a, struct strbuf *b) 交换两个 strbuf。
char *strbuf_detach(struct strbuf *sb, size_t *sz); 将 sb 中的原始内存取出,并将 sz 设置为其 alloc 大小 。
int strbuf_cmp(const struct strbuf *first, const struct strbuf *second); 比较两个 strbuf 的内存是否相同。
void strbuf_reset(struct strbuf *sb); 清空 sb

 上代码

void strbuf_init(struct strbuf *sb, size_t alloc) //初始化
{
    sb->len = 0;
    sb->alloc = alloc;
    sb->buf = (char *)malloc(sizeof(char) * alloc);
    strcpy(sb->buf,"");
}

void strbuf_attach(struct strbuf *sb, void *str, size_t len, size_t alloc) //填充
{

    sb->len = len;
    sb->alloc = alloc;
    sb->buf = (char *)str;
}
void strbuf_release(struct strbuf *sb) //释放
{

    free(sb->buf);
}

void strbuf_swap(struct strbuf *a, struct strbuf *b) //交换
{
    struct strbuf c; //引入中间变量

    c.alloc = a->alloc;
    c.len = a->len;
    c.buf = a->buf;

    a->alloc = b->alloc;
    a->len = b->len;
    a->buf = b->buf;

    b->alloc = c.alloc;
    b->len = c.len;
    b->buf = c.buf;
}

char *strbuf_detach(struct strbuf *sb, size_t *sz) //取出
{
    *sz = sb->alloc;
    return sb->buf;
}
int strbuf_cmp(const struct strbuf *first, const struct strbuf *second) //比较
{
     
    if (first->len == second->len)
        return memcmp(first->buf, second->buf, first->len);
    else
        return 1;
}

void strbuf_reset(struct strbuf *sb) //清空
{
    if (sb == NULL)
        return;
    strbuf_init(sb, sb->alloc);
}

Part 2B

TASK

实现字符串缓冲区类 strbuf 扩容,(追加|插入)字符,字符串等操作。

关于设计一个 C 语言的动态扩容缓冲区

 上代码

void strbuf_grow(struct strbuf *sb, size_t extra) //扩大长度
{
     sb->buf=(char*)realloc(sb->buf,extra);
     strbuf_init(sb,extra);
}

 void strbuf_add(struct strbuf *sb, const void *data, size_t len)//追加数据
{
    if(len==0)
    return ;
    sb->len+=len;
    while(sb->len+1>sb->alloc){
        sb->alloc=sb->len;
        sb->buf=(char*)realloc(sb->buf,sb->alloc+1);
    }
    strcat(sb->buf,(const char*)data);
    sb->buf[sb->len]='\0';
}

void strbuf_addch(struct strbuf *sb, int c)//追加字符c
{
   if(sb->alloc==0){
        sb->alloc=2;
    }
    else{
        if(sb->alloc<=sb->len+2)
        {
            sb->alloc*=2;
        }
    }
  sb->buf=(char*)realloc(sb->buf,(sb->alloc)*sizeof(char));
  sb->len+=1;
  sb->buf[sb->len-1]=c;
  sb->buf[sb->len]='\0';
}


void strbuf_addstr(struct strbuf *sb, const char *s)//追加字符串
{
    if(sb->alloc<(sb->len+strlen(s)+1)) 
    {
        if(sb->alloc==0){
        sb->alloc=strlen(s)+1;
    }
    else{
        sb->alloc=sb->len+strlen(s)+1;
    }
  sb->buf=(char*)realloc(sb->buf,(sb->alloc)*sizeof(char));
    }
    memcpy(sb->buf+sb->len,s,strlen(s));
    sb->len+=strlen(s);
    sb->buf[sb->len]='\0'; 
}


void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2)//追加strbuf
{
      strbuf_addstr(sb,sb2->buf);
}
void strbuf_setlen(struct strbuf *sb, size_t len) //设置长度
{
     while(sb->alloc <= sb->len)
    {
        sb->buf = (char *)realloc(sb->buf,len+1);
        sb->alloc = len;
    }
    sb->len = len ;
    sb->buf[sb->len] ='\0';
}

size_t strbuf_avail(const struct strbuf *sb) //设置空余长度
{
    return sb->alloc - sb->len-1; //总容量—用掉的,不能忘记-1
}

void strbuf_insert(struct strbuf *sb, size_t pos, const void *data, size_t len) //指定位置插入数据
{
    sb->len+=len;
    if(sb->len+1>=sb->alloc)
    {
        sb->alloc=sb->len;
        sb->buf=(char*)realloc((char*)sb->buf,sizeof(char)*(sb->alloc+1));
    }
    char*p=(char*)malloc(sb->len);
    strcpy(p,(const char*)sb->buf+pos);
    strcpy(sb->buf+pos,(const char*)data);
	strcat(sb->buf,(const char*)p);
}

Part 2C

TASK

实现字符串缓冲区类 strbuf 删除部分内容等功能。

关于设计一个 C 语言的动态扩容缓冲区

 继续上代码,哈哈哈

void strbuf_ltrim(struct strbuf *sb) //去除右端所有空格
{
    while ( (sb->buf[0] == ' ' || sb->buf[0] == '\t') && sb->len != 0 )
    {
        memmove(sb->buf, sb->buf+1, sb->len - 1);
        sb->len--;
    }

}

void strbuf_rtrim(struct strbuf *sb) //去除左端所有空格
{
    while (sb->buf[sb->len - 1] == ' ' || sb->buf[sb->len - 1] == '\t')
    {
        sb->buf[sb->len - 1] = '\0';
        if(sb->len <= 1)
            break;
        sb->len--;
    }
}
void strbuf_remove(struct strbuf *sb, size_t pos, size_t len) //删除指定位置和长度的内容
{
    memmove(sb->buf+pos,sb->buf+pos+len,sb->len-pos-len);
    sb->len-=len;
}

Part 2D

  1. 在我们使用 C FILE API 读取一个小文件的时候经常会有这样的一个困惑,我们为什么不能直接一次性读完整个文件到一个大缓冲区呢? 而 fread or read 总是用一个将一个文件中的内容反复读到一个缓冲区中,然后我们从这个缓冲区中取出内容, 但是为什么我们不能直接将一个文件读到一个缓冲区中呢?

  2. 在我们想要从文件或者终端读取一行数据的时候经常有这样的疑惑,我应该用什么函数去读?C++ 的 cin.getline()C++ 的 getline()? C 的 getline()? Python 的 readline()? 正如 * 的网友的总结*,c版本的 getline() 效率是最高的。那么问题来了, C 的 getline() 每次都得我去指定缓冲区和长度... 有什么好的方法让用户可以直接调用一个 strbuf_getline() 无脑的从缓冲区中拿到想要的内容呢?

TASK

关于设计一个 C 语言的动态扩容缓冲区

还是上代码

/*这里呢,涉及到了文件操作函数,句柄,文件描述符*/
ssize_t strbuf_read(struct strbuf *sb, int fd, size_t hint)
{
    FILE*fp=fdopen(fd,"r");

    char p;
    int temp=0;
        sb->buf=(char*)realloc(sb->buf,sizeof(char)*(sb->alloc+=(hint ? hint : 8192)));
         sb->alloc+=(hint ? hint : 8192);
        while((p=fgetc(fp))!=-1)
        {
            sb->buf[sb->len++]=p;
            temp=1;
        }
    if(temp==1)
       sb->buf[sb->len]='\0';
    return 1;
}
    

int strbuf_getline(struct strbuf *sb, FILE *fp)
{  
      char p;
      while((p=fgetc(fp))!=EOF){
      if(p=='\n'||feof(fp)!=0){
        break;
      }
      sb->buf=(char*)realloc(sb->buf,(sb->alloc)*sizeof(char));
      sb->buf[sb->len]=p;
      sb->len++; 
    }
     sb->buf[sb->len]='\0';
     return sb->len;

}

接下来呢是一个小小挑战(其实对我来说是一个大挑战) 

关于设计一个 C 语言的动态扩容缓冲区

代码代码,啦啦啦

struct strbuf **strbuf_split_buf(const char *str, size_t len, int terminator, int max)
//实现字符串切割(C 系字符串函数的一个痛点)
{
    struct strbuf **buf_list = (struct strbuf **)malloc(sizeof(struct strbuf *) * max);

    const char *end = str + len;
    const char *first = str;
    const char *second;
    int nslice = 0;
    while (*first == terminator)
        first++;
    for (second = first + 1; second <= end; second++)
    {
        if (*second == terminator)
        {
            int len_tmp;
            len_tmp = second - first;
            buf_list[nslice]->len = len_tmp;
            buf_list[nslice]->alloc = len_tmp+1;
            buf_list[nslice]->buf = (char *)malloc(sizeof(char) * len_tmp);
            memcpy(buf_list[nslice]->buf, first, len_tmp);
            *(buf_list[nslice]->buf+len_tmp)='\0';
            nslice++;
            while (*second == terminator && second <= end)
                second++;
            first = second;
        }
        if (nslice == max)
            break;
    }

    return buf_list;
}
bool strbuf_begin_judge(char *target_str, const char *str, int strnlen)
//实现判断一个 strbuf 是否以指定字符串开头的功能
{
     
    if(strnlen == 0)
        return true;
    if(strlen(target_str) < strnlen)
        return false;
    if(memcmp(target_str,str,strnlen - 1 ) == 0)
        return true;
    else
        return false;
}
char *strbuf_get_mid_buf(char *target_buf, int begin, int end, int len)
//获取字符串从坐标 [begin, end) 的所有内容(可以分成引用和拷贝两个模式)
{
    if (end < 0 || end > begin || end > len)
        return NULL;
    int mid_len = end - begin + 1;
    char *str_tmp = (char *)malloc(mid_len);
    memcpy(str_tmp, target_buf + begin, mid_len);
    return str_tmp;
}

最后呢,将代码汇总一下叭,敲代码过程中可能会有小小错误,希望多多理解包含,指出错误呐 

#ifndef _STRBUF_H
#define _STRBUF_H

#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

struct strbuf {
    int len;
    int alloc;
    char *buf;
};

//2A部分
void strbuf_init(struct strbuf *sb, size_t alloc);
void strbuf_attach(struct strbuf *sb, void *str, size_t len, size_t alloc);
void strbuf_release(struct strbuf *sb);
void strbuf_swap(struct strbuf *a, struct strbuf *b);
char *strbuf_detach(struct strbuf *sb, size_t *sz);
int strbuf_cmp(const struct strbuf *first, const struct strbuf *second);
void strbuf_reset(struct strbuf *sb);

//2B部分

void strbuf_grow(struct strbuf *sb, size_t extra);
void strbuf_add(struct strbuf *sb, const void *data, size_t len);
void strbuf_addch(struct strbuf *sb, int c);
void strbuf_addstr(struct strbuf *sb, const char *s);
void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2);
void strbuf_setlen(struct strbuf *sb, size_t len);
size_t strbuf_avail(const struct strbuf *sb);
void strbuf_insert(struct strbuf *sb, size_t pos, const void *data, size_t len);

//2C部分

void strbuf_rtrim(struct strbuf *sb);
void strbuf_ltrim(struct strbuf *sb);
void strbuf_remove(struct strbuf *sb, size_t pos, size_t len);

//2D部分

ssize_t strbuf_read(struct strbuf *sb, int fd, size_t hint);
int strbuf_getline(struct strbuf *sb, FILE *fp);
//CHALLENGE
struct strbuf **strbuf_split_buf(const char *str, size_t len, int terminator, int max);
bool strbuf_begin_judge(char *target_str, const char *str, int strnlen);
char *strbuf_get_mid_buf(char *target_buf, int begin, int end, int len);


//2A部分

void strbuf_init(struct strbuf *sb, size_t alloc) //初始化
{
    sb->len = 0;
    sb->alloc = alloc;
    sb->buf = (char *)malloc(sizeof(char) * alloc);
    strcpy(sb->buf,"");
}

void strbuf_attach(struct strbuf *sb, void *str, size_t len, size_t alloc) //填充
{

    sb->len = len;
    sb->alloc = alloc;
    sb->buf = (char *)str;
}

void strbuf_release(struct strbuf *sb) //释放
{

    free(sb->buf);
}

void strbuf_swap(struct strbuf *a, struct strbuf *b) //交换
{
    struct strbuf c; //引入中间变量

    c.alloc = a->alloc;
    c.len = a->len;
    c.buf = a->buf;

    a->alloc = b->alloc;
    a->len = b->len;
    a->buf = b->buf;

    b->alloc = c.alloc;
    b->len = c.len;
    b->buf = c.buf;
}

char *strbuf_detach(struct strbuf *sb, size_t *sz) //取出
{
    *sz = sb->alloc;
    return sb->buf;
}

int strbuf_cmp(const struct strbuf *first, const struct strbuf *second) //比较
{
     
    if (first->len == second->len)
        return memcmp(first->buf, second->buf, first->len);
    else
        return 1;
}

void strbuf_reset(struct strbuf *sb) //清空
{
    if (sb == NULL)
        return;
    strbuf_init(sb, sb->alloc);
}

//2B部分

void strbuf_grow(struct strbuf *sb, size_t extra) //扩大长度
{
     sb->buf=(char*)realloc(sb->buf,extra);
     strbuf_init(sb,extra);
}

 void strbuf_add(struct strbuf *sb, const void *data, size_t len)//追加数据
{
    if(len==0)
    return ;
    sb->len+=len;
    while(sb->len+1>sb->alloc){
        sb->alloc=sb->len;
        sb->buf=(char*)realloc(sb->buf,sb->alloc+1);
    }
    strcat(sb->buf,(const char*)data);
    sb->buf[sb->len]='\0';
}

void strbuf_addch(struct strbuf *sb, int c)//追加字符c
{
   if(sb->alloc==0){
        sb->alloc=2;
    }
    else{
        if(sb->alloc<=sb->len+2)
        {
            sb->alloc*=2;
        }
    }
  sb->buf=(char*)realloc(sb->buf,(sb->alloc)*sizeof(char));
  sb->len+=1;
  sb->buf[sb->len-1]=c;
  sb->buf[sb->len]='\0';
}

void strbuf_addstr(struct strbuf *sb, const char *s)//追加字符串
{
    if(sb->alloc<(sb->len+strlen(s)+1)) 
    {
        if(sb->alloc==0){
        sb->alloc=strlen(s)+1;
    }
    else{
        sb->alloc=sb->len+strlen(s)+1;
    }
  sb->buf=(char*)realloc(sb->buf,(sb->alloc)*sizeof(char));
    }
    memcpy(sb->buf+sb->len,s,strlen(s));
    sb->len+=strlen(s);
    sb->buf[sb->len]='\0'; 
}


void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2)//追加strbuf
{
      strbuf_addstr(sb,sb2->buf);
}

void strbuf_setlen(struct strbuf *sb, size_t len) //设置长度
{
     while(sb->alloc <= sb->len)
    {
        sb->buf = (char *)realloc(sb->buf,len+1);
        sb->alloc = len;
    }
    sb->len = len ;
    sb->buf[sb->len] ='\0';
}

size_t strbuf_avail(const struct strbuf *sb) //设置空余长度
{
    return sb->alloc - sb->len-1; //总容量—用掉的,不能忘记-1
}

void strbuf_insert(struct strbuf *sb, size_t pos, const void *data, size_t len) //指定位置插入数据
{
    sb->len+=len;
    if(sb->len+1>=sb->alloc)
    {
        sb->alloc=sb->len;
        sb->buf=(char*)realloc((char*)sb->buf,sizeof(char)*(sb->alloc+1));
    }
    char*p=(char*)malloc(sb->len);
    strcpy(p,(const char*)sb->buf+pos);
    strcpy(sb->buf+pos,(const char*)data);
	strcat(sb->buf,(const char*)p);
}

//2C部分

void strbuf_ltrim(struct strbuf *sb) //去除右端所有空格
{
    while ( (sb->buf[0] == ' ' || sb->buf[0] == '\t') && sb->len != 0 )
    {
        memmove(sb->buf, sb->buf+1, sb->len - 1);
        sb->len--;
    }

}

void strbuf_rtrim(struct strbuf *sb) //去除左端所有空格
{
    while (sb->buf[sb->len - 1] == ' ' || sb->buf[sb->len - 1] == '\t')
    {
        sb->buf[sb->len - 1] = '\0';
        if(sb->len <= 1)
            break;
        sb->len--;
    }
}

void strbuf_remove(struct strbuf *sb, size_t pos, size_t len) //删除指定位置和长度的内容
{
    memmove(sb->buf+pos,sb->buf+pos+len,sb->len-pos-len);
    sb->len-=len;
}

//2D部分

/*这里呢,涉及到了文件操作函数,句柄,文件描述符*/

ssize_t strbuf_read(struct strbuf *sb, int fd, size_t hint)
{
    FILE*fp=fdopen(fd,"r");

    char p;
    int temp=0;
        sb->buf=(char*)realloc(sb->buf,sizeof(char)*(sb->alloc+=(hint ? hint : 8192)));
         sb->alloc+=(hint ? hint : 8192);
        while((p=fgetc(fp))!=-1)
        {
            sb->buf[sb->len++]=p;
            temp=1;
        }
    if(temp==1)
       sb->buf[sb->len]='\0';
    return 1;
}
    

int strbuf_getline(struct strbuf *sb, FILE *fp)
{  
      char p;
      while((p=fgetc(fp))!=EOF){
      if(p=='\n'||feof(fp)!=0){
        break;
      }
      sb->buf=(char*)realloc(sb->buf,(sb->alloc)*sizeof(char));
      sb->buf[sb->len]=p;
      sb->len++; 
    }
     sb->buf[sb->len]='\0';
     return sb->len;

}

//challenge

struct strbuf **strbuf_split_buf(const char *str, size_t len, int terminator, int max)
//实现字符串切割(C 系字符串函数的一个痛点)
{
    struct strbuf **buf_list = (struct strbuf **)malloc(sizeof(struct strbuf *) * max);

    const char *end = str + len;
    const char *first = str;
    const char *second;
    int nslice = 0;
    while (*first == terminator)
        first++;
    for (second = first + 1; second <= end; second++)
    {
        if (*second == terminator)
        {
            int len_tmp;
            len_tmp = second - first;
            buf_list[nslice]->len = len_tmp;
            buf_list[nslice]->alloc = len_tmp+1;
            buf_list[nslice]->buf = (char *)malloc(sizeof(char) * len_tmp);
            memcpy(buf_list[nslice]->buf, first, len_tmp);
            *(buf_list[nslice]->buf+len_tmp)='\0';
            nslice++;
            while (*second == terminator && second <= end)
                second++;
            first = second;
        }
        if (nslice == max)
            break;
    }

    return buf_list;
}

bool strbuf_begin_judge(char *target_str, const char *str, int strnlen)
//实现判断一个 strbuf 是否以指定字符串开头的功能
{
     
    if(strnlen == 0)
        return true;
    if(strlen(target_str) < strnlen)
        return false;
    if(memcmp(target_str,str,strnlen - 1 ) == 0)
        return true;
    else
        return false;
}

char *strbuf_get_mid_buf(char *target_buf, int begin, int end, int len)
//获取字符串从坐标 [begin, end) 的所有内容(可以分成引用和拷贝两个模式)
{
    if (end < 0 || end > begin || end > len)
        return NULL;
    int mid_len = end - begin + 1;
    char *str_tmp = (char *)malloc(mid_len);
    memcpy(str_tmp, target_buf + begin, mid_len);
    return str_tmp;
}

#endif

小小总结一下☃☃

磕磕盼盼的写完,虽然代码写的不好,但是做这个项目可以学到好多知识,如果你感兴趣的话可以上手来敲敲代码哦

把冬天的不开心埋进雪人里,等来年长出新的期待,下雪快乐吖☃☃

上一篇:Hadoop集群部署


下一篇:APP内存占用测试