redis源码之sds

        sds是redis中一个很重要的数据结构,今天看学一下它源码的实现。

typedef char *sds;

/* Note: sdshdr5 is never used, we just access the flags byte directly.
 * However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */ //flags字段的高5bits保存字符串实际长度
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* used */  //有效字符串长度
    uint8_t alloc; /* excluding the header and null terminator */ //alloc-分配的内存空间长度
    unsigned char flags; /* 3 lsb of type, 5 unused bits */ //头部类型
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

#define SDS_TYPE_5  0
#define SDS_TYPE_8  1
#define SDS_TYPE_16 2
#define SDS_TYPE_32 3
#define SDS_TYPE_64 4
#define SDS_TYPE_MASK 7
#define SDS_TYPE_BITS 3
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)

__attribute__ ((__packed__))是避免内存字节对齐节省内存,上面定义了5个结构体,redis会根据字符串的长度来选择合适的结构体,每个结构体有对应数据部分和头部。下面具体学习sds 的一些API。

static inline int sdsHdrSize(char type) {//根据结构体类型返回sds头部长度(头部字节相加的结果)
    switch(type & SDS_TYPE_MASK) {
        case SDS_TYPE_5:
            return sizeof(struct sdshdr5);  //1 sdshdr5对应buf里面没有设定大小,应为空
        case SDS_TYPE_8:
            return sizeof(struct sdshdr8);  //3
        case SDS_TYPE_16:
            return sizeof(struct sdshdr16);  //5 
        case SDS_TYPE_32:
            return sizeof(struct sdshdr32);   //9
        case SDS_TYPE_64:
            return sizeof(struct sdshdr64);   //17
    }
    return 0;
}

static inline char sdsReqType(size_t string_size) {   根据size来选取合适的结构体
    if (string_size < 1<<5)  //1<<5 = 32。SDS_TYPE_5 高5位存储字符串的实际长度,低3位存储type
        return SDS_TYPE_5;
    if (string_size < 1<<8)  //1<<8 = 256   SDS_TYPE_8用8个位存储长度
        return SDS_TYPE_8;
    if (string_size < 1<<16) //1<<16 = 65526
        return SDS_TYPE_16;
#if (LONG_MAX == LLONG_MAX)
    if (string_size < 1ll<<32)
        return SDS_TYPE_32;
    return SDS_TYPE_64;
#else
    return SDS_TYPE_32;
#endif
}

sdsReqType中的string_size是对应字符串的长度,这里注意每种结构体能存储串长是不一样的,因为串长的位不一样。sdsHdrSize用来获取对应的type的头部长度。

sds sdsnewlen(const void *init, size_t initlen) {//根据给定的初始化字符串 init 和字符串长度 initlen,创建一个新的sds
    void *sh;
    sds s;    //字符串char *
	
    char type = sdsReqType(initlen);   根据sds长度选择结构体类型,
    /* Empty strings are usually created in order to append. Use type 8
     * since type 5 is not good at this. */
     
    if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;      //SDS_TYPE_5
	
    int hdrlen = sdsHdrSize(type);  返回相应结构体头部对应的大小
	
    unsigned char *fp; /* flags pointer. */

    sh = s_malloc(hdrlen+initlen+1); //分配内存   initlen+2 ,利用结构体的大小分配内存
	
    if (init==SDS_NOINIT)
        init = NULL;
    else if (!init)
        memset(sh, 0, hdrlen+initlen+1); //初始化
	
    if (sh == NULL) return NULL;
	
    s = (char*)sh+hdrlen;  //指向第一个有效数据
    fp = ((unsigned char*)s)-1;  //fp指向flag
	
    switch(type) {
        case SDS_TYPE_5: {
            *fp = type | (initlen << SDS_TYPE_BITS);//0000 0011 << 3 = 0001 1000 //高5位保存字符串的长度,低3位保存的是type
            break;
        }
        case SDS_TYPE_8: {
            SDS_HDR_VAR(8,s);//让sh指向整个结构体的起始地址
            sh->len = initlen;
            sh->alloc = initlen;
            *fp = type;
            break;
        }
        case SDS_TYPE_16: {
            SDS_HDR_VAR(16,s);
            sh->len = initlen;
            sh->alloc = initlen;
            *fp = type;
            break;
        }
        case SDS_TYPE_32: {
            SDS_HDR_VAR(32,s);
            sh->len = initlen;
            sh->alloc = initlen;
            *fp = type;
            break;
        }
        case SDS_TYPE_64: {
            SDS_HDR_VAR(64,s);
            sh->len = initlen;
            sh->alloc = initlen;
            *fp = type;
            break;
        }
    }
    if (initlen && init)
        memcpy(s, init, initlen);
    s[initlen] = '\0';
    return s;
}

sdsnewlen函数根据传入的initlen的大小会选择不同的结构体类型。

void sdsfree(sds s) {
    if (s == NULL) return;
    s_free((char*)s-sdsHdrSize(s[-1]));
}

sdsfree这里的s是指向第一个有效数据的,sdsHdrSize(s[-1]))返回其对应的头结构大小,(char*)s-sdsHdrSize(s[-1])的结果就是s指向结构体的首地址。

/* Modify an sds string in-place to make it empty (zero length).
 * However all the existing buffer is not discarded but set as free space
 * so that next append operations will not require allocations up to the
 * number of bytes previously available. */
void sdsclear(sds s) { //在不释放 SDS 的字符串空间的情况下,重置 SDS 所保存的字符串为空字符串。
    sdssetlen(s, 0);  //设置长度为0
    s[0] = '\0';
}

/* Enlarge the free space at the end of the sds string so that the caller
 * is sure that after calling this function can overwrite up to addlen
 * bytes after the end of the string, plus one more byte for nul term.
 *
 * Note: this does not change the *length* of the sds string as returned
 * by sdslen(), but only the free buffer space we have. */
 //对sds中buf的长度进行扩展,确保在函数执行之后,buf至少会有addlen+1长度的空余空间(额外的1字节是为\0准备的)
sds sdsMakeRoomFor(sds s, size_t addlen) {//addlen是需要扩展的长度 
    void *sh, *newsh;
    size_t avail = sdsavail(s); //获取s目前的空余空间长度
    
    size_t len, newlen;
    char type, oldtype = s[-1] & SDS_TYPE_MASK;  //取低3位,低3位存储结构体类型。oldtype表示结构体类型
    int hdrlen;

    /* Return ASAP if there is enough space left. */
    if (avail >= addlen) return s;//s目前的空余空间已经足够,无须再进行扩展,直接返回

    len = sdslen(s);//获取s目前已占用空间的长度
	
    sh = (char*)s-sdsHdrSize(oldtype);          //sh指向结构体起始地址
    newlen = (len+addlen);// s 最少需要的长度

	// 根据新长度,为 s 分配新空间所需的大小
    if (newlen < SDS_MAX_PREALLOC)
		// 如果新长度小于 SDS_MAX_PREALLOC 
        // 那么为它分配两倍于所需长度的空间
        newlen *= 2;
    else
		// 否则,分配长度为目前长度加上 SDS_MAX_PREALLOC
        newlen += SDS_MAX_PREALLOC;

    type = sdsReqType(newlen);   //type可以指明存放长度所需要的字节

    /* Don't use type 5: the user is appending to the string and type 5 is
     * not able to remember empty space, so sdsMakeRoomFor() must be called
     * at every appending operation. */
    if (type == SDS_TYPE_5) type = SDS_TYPE_8;

    hdrlen = sdsHdrSize(type);//hdrlen表示头部所需要的长度
	
    if (oldtype==type) {
        newsh = s_realloc(sh, hdrlen+newlen+1);
        if (newsh == NULL) return NULL;// 内存不足,分配失败,返回
        s = (char*)newsh+hdrlen;
    } else {
        /* Since the header size changes, need to move the string forward,
         * and can't use realloc */
        newsh = s_malloc(hdrlen+newlen+1);
        if (newsh == NULL) return NULL;
        memcpy((char*)newsh+hdrlen, s, len+1);
        s_free(sh);
        s = (char*)newsh+hdrlen;
        s[-1] = type;
        sdssetlen(s, len); //设置已经使用了空间的长度
    }
    sdssetalloc(s, newlen);//设置分配空间的长度
    return s;
}

注意这里扩展可都会改变结构体的类型。

/* Reallocate the sds string so that it has no free space at the end. The
 * contained string remains not altered, but next concatenation operations
 * will require a reallocation.
 *
 * After the call, the passed sds string is no longer valid and all the
 * references must be substituted with the new pointer returned by the call. */
sds sdsRemoveFreeSpace(sds s) {//回收sds中的空闲空间,回收不会对sds中保存的字符串内容做任何修改。
    void *sh, *newsh;
    char type, oldtype = s[-1] & SDS_TYPE_MASK;  //oldtype表示结构体类型
    int hdrlen, oldhdrlen = sdsHdrSize(oldtype);//oldhdrlen表示结构体头部的长度
	
    size_t len = sdslen(s);  //返回sds实际保存的字符串的长度
    size_t avail = sdsavail(s);  //获取剩余空间的长度
    sh = (char*)s-oldhdrlen;

    /* Return ASAP if there is no space left. */
    if (avail == 0) return s;

    /* Check what would be the minimum SDS header that is just good enough to
     * fit this string. */
     //上面的oldtype是根据总长算出来的type
    type = sdsReqType(len);//根据sds有效串长度 计算结构体类型
    hdrlen = sdsHdrSize(type);根据结构体类型决定sds头部长度

    /* If the type is the same, or at least a large enough type is still
     * required, we just realloc(), letting the allocator to do the copy
     * only if really needed. Otherwise if the change is huge, we manually
     * reallocate the string to use the different header type. */
    if (oldtype==type || type > SDS_TYPE_8) {//判断结构体类型是否发生改变
        newsh = s_realloc(sh, oldhdrlen+len+1);  //压缩空间
        if (newsh == NULL) return NULL;
        s = (char*)newsh+oldhdrlen;
    } else {
        newsh = s_malloc(hdrlen+len+1);//进行内存重分配,让 buf 的长度仅仅足够保存字符串内容
        if (newsh == NULL) return NULL;
        memcpy((char*)newsh+hdrlen, s, len+1);
        s_free(sh);
        s = (char*)newsh+hdrlen;
        s[-1] = type;
        sdssetlen(s, len);
    }
    sdssetalloc(s, len);
    return s;
}

这里回收空间时也有可能改变结构体的类型。

/* Increment the sds length and decrements the left free space at the
 * end of the string according to 'incr'. Also set the null term
 * in the new end of the string.
 *
 * This function is used in order to fix the string length after the
 * user calls sdsMakeRoomFor(), writes something after the end of
 * the current string, and finally needs to set the new length.
 *
 * Note: it is possible to use a negative increment in order to
 * right-trim the string.
 *
 * Usage example:
 *
 * Using sdsIncrLen() and sdsMakeRoomFor() it is possible to mount the
 * following schema, to cat bytes coming from the kernel to the end of an
 * sds string without copying into an intermediate buffer:
 *
 * oldlen = sdslen(s);
 * s = sdsMakeRoomFor(s, BUFFER_SIZE);
 * nread = read(fd, s+oldlen, BUFFER_SIZE);
 * ... check for nread <= 0 and handle it ...
 * sdsIncrLen(s, nread);
 */
 //正数为增加,负数为缩减
void sdsIncrLen(sds s, ssize_t incr) {//根据incr参数,增加sds的长度,缩减空余空间,并将\0放到新字符串的尾端
    unsigned char flags = s[-1];  
    size_t len;
    switch(flags & SDS_TYPE_MASK) {
        case SDS_TYPE_5: {
            unsigned char *fp = ((unsigned char*)s)-1;
            unsigned char oldlen = SDS_TYPE_5_LEN(flags);  //oldlen字符串的长度
            assert((incr > 0 && oldlen+incr < 32) || (incr < 0 && oldlen >= (unsigned int)(-incr)));
            *fp = SDS_TYPE_5 | ((oldlen+incr) << SDS_TYPE_BITS);  //高5位保存长度
            len = oldlen+incr;  //修正len值
            break;
        }
        case SDS_TYPE_8: {
            SDS_HDR_VAR(8,s);
            assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr)));
            len = (sh->len += incr);
            break;
        }
        case SDS_TYPE_16: {
            SDS_HDR_VAR(16,s);
            assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr)));
            len = (sh->len += incr);
            break;
        }
        case SDS_TYPE_32: {
            SDS_HDR_VAR(32,s);
            assert((incr >= 0 && sh->alloc-sh->len >= (unsigned int)incr) || (incr < 0 && sh->len >= (unsigned int)(-incr)));
            len = (sh->len += incr);
            break;
        }
        case SDS_TYPE_64: {
            SDS_HDR_VAR(64,s);
            assert((incr >= 0 && sh->alloc-sh->len >= (uint64_t)incr) || (incr < 0 && sh->len >= (uint64_t)(-incr)));
            len = (sh->len += incr);
            break;
        }
        default: len = 0; /* Just to avoid compilation warnings. */
    }
    s[len] = '\0';
}

注意这里时对有效字符串进行操作,但是空间不能超过规定的最大值。

/* Identical sdsll2str(), but for unsigned long long type. */
int sdsull2str(char *s, unsigned long long v) {
    char *p, aux;
    size_t l;

    /* Generate the string representation, this method produces
     * an reversed string. */
    p = s;
    do {
        *p++ = '0'+(v%10);
        v /= 10;
    } while(v);

    /* Compute length and add null term. */
    l = p-s;
    *p = '\0';

    /* Reverse the string. */
    p--;
    while(s < p) {
        aux = *s;
        *s = *p;
        *p = aux;
        s++;
        p--;
    }
    return l;
}

long long 类型转换为字符串,逐个扫描,最后进行反转。

/* Like sdscatprintf() but gets va_list instead of being variadic. */
sds sdscatvprintf(sds s, const char *fmt, va_list ap) {//将ap对应的参数拼接成字符串,fmt表示匹配模式
    va_list cpy;
    char staticbuf[1024], *buf = staticbuf, *t;
	
    size_t buflen = strlen(fmt)*2;

    /* We try to start using a static buffer for speed.
     * If not possible we revert to heap allocation. */
    if (buflen > sizeof(staticbuf)) {
        buf = s_malloc(buflen);
        if (buf == NULL) return NULL;
    } else {
        buflen = sizeof(staticbuf);
    }

    /* Try with buffers two times bigger every time we fail to
     * fit the string in the current buffer size. */
    while(1) {
        buf[buflen-2] = '\0';
        va_copy(cpy,ap);  //复制宏
        vsnprintf(buf, buflen, fmt, cpy);//fmt指定输出格式的字符串,它决定了你需要提供的可变参数的类型、个数和顺序
        va_end(cpy);
		
        if (buf[buflen-2] != '\0') {   //防止buf大小不够一次读不完
            if (buf != staticbuf) s_free(buf);
            buflen *= 2;
            buf = s_malloc(buflen);
            if (buf == NULL) return NULL;
            continue;
        }
        break;
    }

    /* Finally concat the obtained string to the SDS string and return it. */
    t = sdscat(s, buf);
    if (buf != staticbuf) s_free(buf);
    return t;
}

sdscatvprintf类似字符串拼接功能。

/* This function is similar to sdscatprintf, but much faster as it does
 * not rely on sprintf() family functions implemented by the libc that
 * are often very slow. Moreover directly handling the sds string as
 * new data is concatenated provides a performance improvement.
 *
 * However this function only handles an incompatible subset of printf-alike
 * format specifiers:
 *
 * %s - C String
 * %S - SDS string
 * %i - signed int
 * %I - 64 bit signed integer (long long, int64_t)
 * %u - unsigned int
 * %U - 64 bit unsigned integer (unsigned long long, uint64_t)
 * %% - Verbatim "%" character.
 */
sds sdscatfmt(sds s, char const *fmt, ...) {//格式化连接字符串
    size_t initlen = sdslen(s); //获取串长
    const char *f = fmt;
    long i;
    va_list ap;

    va_start(ap,fmt);
    f = fmt;    /* Next format specifier byte to process. */
    i = initlen; /* Position of the next byte to write to dest str. */
	
    while(*f) {
        char next, *str;
        size_t l;
        long long num;
        unsigned long long unum;

        /* Make sure there is always space for at least 1 char. */
        if (sdsavail(s)==0) {
            s = sdsMakeRoomFor(s,1);
        }

        switch(*f) {
        case '%':
            next = *(f+1);  //后移
            f++;   //后移
            switch(next) {
            case 's':
            case 'S':
                str = va_arg(ap,char*);  //获取对应元素
                printf("str =%s\n",str);
                l = (next == 's') ? strlen(str) : sdslen(str);
                if (sdsavail(s) < l) {
                    s = sdsMakeRoomFor(s,l);
                }
                memcpy(s+i,str,l);
                sdsinclen(s,l); //修改串长
                i += l;
                break;
            case 'i':
            case 'I':
                if (next == 'i')
                    num = va_arg(ap,int);
                else
                    num = va_arg(ap,long long);
                {
                    char buf[SDS_LLSTR_SIZE];
                    l = sdsll2str(buf,num);
                    if (sdsavail(s) < l) {
                        s = sdsMakeRoomFor(s,l);
                    }
                    memcpy(s+i,buf,l);
                    sdsinclen(s,l);
                    i += l;
                }
                break;
            case 'u':
            case 'U':
                if (next == 'u')
                    unum = va_arg(ap,unsigned int);
                else
                    unum = va_arg(ap,unsigned long long);
                {
                    char buf[SDS_LLSTR_SIZE];
                    l = sdsull2str(buf,unum);
                    if (sdsavail(s) < l) {
                        s = sdsMakeRoomFor(s,l);
                    }
                    memcpy(s+i,buf,l);
                    sdsinclen(s,l);
                    i += l;
                }
                break;
            default: /* Handle %% and generally %<unknown>. */
                s[i++] = next;
                sdsinclen(s,1);
                break;
            }
            break;
        default:
            s[i++] = *f;
            sdsinclen(s,1);
            break;
        }
        f++;
    }
    va_end(ap);

    /* Add null-term */
    s[i] = '\0';
    return s;
}

此函数与sdscatprintf类似,但速度要快得多。

sds sdstrim(sds s, const char *cset) {//对sds左右两端进行修剪,清除其中cset指定的所有字符,sdsstrim(xxyyabcyyxy, "xy") 将返回 "abc"
    char *start, *end, *sp, *ep;
    size_t len;

    sp = start = s;
    ep = end = s+sdslen(s)-1; //最后一个字符
	
    while(sp <= end && strchr(cset, *sp)) sp++;  //strchr一个串中查找给定字符的第一个匹配之处
    while(ep > sp && strchr(cset, *ep)) ep--;
	
    len = (sp > ep) ? 0 : ((ep-sp)+1);
	
    if (s != sp) memmove(s, sp, len); //memmove字节拷贝
	
    s[len] = '\0';
    sdssetlen(s,len);
    return s;
}

该算法从左边和右边都遍历一次达到两边裁剪的目的。

/* Turn the string into a smaller (or equal) string containing only the
 * substring specified by the 'start' and 'end' indexes.
 *
 * start and end can be negative, where -1 means the last character of the
 * string, -2 the penultimate character, and so forth.
 *
 * The interval is inclusive, so the start and end characters will be part
 * of the resulting string.
 *
 * The string is modified in-place.
 *
 * Example:
 *
 * s = sdsnew("Hello World");
 * sdsrange(s,1,-1); => "ello World"
 */
void sdsrange(sds s, ssize_t start, ssize_t end) { //按索引对截取sds字符串的其中一段,start和end都是闭区间(包含在内)
    size_t newlen, len = sdslen(s);

    if (len == 0) return;
    if (start < 0) {
        start = len+start;
        if (start < 0) start = 0;
    }
    if (end < 0) {
        end = len+end;
        if (end < 0) end = 0;
    }
    newlen = (start > end) ? 0 : (end-start)+1;
    if (newlen != 0) {
        if (start >= (ssize_t)len) {
            newlen = 0;
        } else if (end >= (ssize_t)len) {
            end = len-1;
            newlen = (start > end) ? 0 : (end-start)+1;
        }
    } else {
        start = 0;
    }
    if (start && newlen) memmove(s, s+start, newlen);
    s[newlen] = 0;
    sdssetlen(s,newlen);
}

注意start和end都可能为负值,其中-1表示末尾的字符,-2表示倒数第二个字符,依次类推。

/* Split 's' with separator in 'sep'. An array
 * of sds strings is returned. *count will be set
 * by reference to the number of tokens returned.
 *
 * On out of memory, zero length string, zero length
 * separator, NULL is returned.
 *
 * Note that 'sep' is able to split a string using
 * a multi-character separator. For example
 * sdssplit("foo_-_bar","_-_"); will return two
 * elements "foo" and "bar".
 *
 * This version of the function is binary-safe but
 * requires length arguments. sdssplit() is just the
 * same function but for zero-terminated strings.
 */
 //使用分隔符sep对s进行分割,返回一个 sds 字符串的数组。count 会被设置为返回数组元素的数量。
sds *sdssplitlen(const char *s, ssize_t len, const char *sep, int seplen, int *count) {
    int elements = 0, slots = 5; 
    long start = 0, j;
    sds *tokens;  //字符串数组

    if (seplen < 1 || len < 0) return NULL;

    tokens = s_malloc(sizeof(sds)*slots);  //sizeof(sds) = 8
	
    if (tokens == NULL) return NULL;

    if (len == 0) {
        *count = 0;
        return tokens;
    }
	
    for (j = 0; j < (len-(seplen-1)); j++) {
        /* make sure there is room for the next element and the final one */
        if (slots < elements+2) {  //elements是下标
            sds *newtokens;

            slots *= 2;
            newtokens = s_realloc(tokens,sizeof(sds)*slots);//tokens空间不够重新分配内存
            if (newtokens == NULL) goto cleanup;
            tokens = newtokens;
        }
		
        /* search the separator */
        if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) { //字符串比较
            tokens[elements] = sdsnewlen(s+start,j-start);
            if (tokens[elements] == NULL) goto cleanup;
            elements++;
            start = j+seplen; //前进
            j = j+seplen-1; /* skip the separator */
        }
    }
	
    /* Add the final element. We are sure there is room in the tokens array. */
    tokens[elements] = sdsnewlen(s+start,len-start);
    if (tokens[elements] == NULL) goto cleanup;
    elements++;
    *count = elements;
    return tokens;

cleanup:
    {
        int i;
        for (i = 0; i < elements; i++) sdsfree(tokens[i]);
        s_free(tokens);
        *count = 0;
        return NULL;
    }
}

遍历字符串并和分隔符进行比较,然后将字符串保存在字符数组中。

sds sdscatrepr(sds s, const char *p, size_t len) {//将长度为 len 的字符串p以带引号的格式,追加到给定sds的末尾
    s = sdscatlen(s,"\"",1);
    while(len--) {
        switch(*p) {
        case '\\':
        case '"':
            s = sdscatprintf(s,"\\%c",*p);
            break;
        case '\n': s = sdscatlen(s,"\\n",2); break;  //  \\表示转义
        case '\r': s = sdscatlen(s,"\\r",2); break;
        case '\t': s = sdscatlen(s,"\\t",2); break;
        case '\a': s = sdscatlen(s,"\\a",2); break;
        case '\b': s = sdscatlen(s,"\\b",2); break;
        default:
            if (isprint(*p)) //判断字符c是否为可打印字符
                s = sdscatprintf(s,"%c",*p);
            else
                s = sdscatprintf(s,"\\x%02x",(unsigned char)*p);
            break;
        }
        p++;
    }
    return sdscatlen(s,"\"",1);
}

遍历字符串,然后格式化输入。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

盼盼编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值