[SDS阅读理解/9]源码中的函数/6

感觉分的太散了,每篇才几个函数,导致章节太多…不过还是先记录完再纠结这个问题吧。

       第二十六个。该函数的作用和第二十五的差不多,只不过速度会快一点,因为函数内部没有使用C标准库中的类sprintf()函数(作者说这类函数的速度很慢…),但是处理的格式没那么全,如浮点格式的%f,且有些格式是作者自创的,如%U64位无符号整形格式。这些说明在作者注释里都有提到。关于变参问题,我搜索了解了下,发现这篇百科解释的挺清楚的,虽然具体内部原理还不清楚,但相关概念还是知道了,这样有易于理解代码。

// 作者注释
/* 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.
 */

// 这里注意下-char const *fmt其实和const char *fmt一样
// 意思是fmt这个指针可以变,但它指向的内容*fmt不可变
sds sdscatfmt(sds s, char const *fmt, ...) {
    // 先获取s变量里已有的字符个数,下面这个函数前面章节有讲
    size_t initlen = sdslen(s);
    const char *f = fmt;
    int i;
    // 该变量用于记录变参地址
    va_list ap;

    // 根据fmt的地址来获取第一个变参的地址并存到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. */
    // 下面的循环就是遍历fmt字符串中的每个字符,将不同格式的字符格式化后
    // 存到变量s中
    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. */
        // 确保变量s必须至少有一字节的可用空间(也就是说s必须指向合法空间)
        // 下面这个函数返回s的可用空间的字节数,如果没用可用空间则返回0
        // 原理前面章节有讲
        if (sdsavail(s)==0) {
            // 可用空间为0,调用下面这个函数为s分配空间
            // 函数原理前面章节有讲
            s = sdsMakeRoomFor(s,1);
        }

        // 判断单个字符的情况
        switch(*f) {
        // 如果是百分号'%'
        case '%':
            // 获取下一个字符
            next = *(f+1);
            // 指针前移一位
            f++;
            // 判断该字符的情况
            switch(next) {
            // 如果是小写的's'或是大写的'S'(格式化字符)
            case 's':
            case 'S':
                // 获取第一个变参的地址并将ap指向下一个变参的地址
                str = va_arg(ap,char*);
                // 如果是小写的's',就调用strlen()计算str的字符个数
                // 否则就调用sdslen()计算str的字符个数
                l = (next == 's') ? strlen(str) : sdslen(str);
                // 如果s的可用空间小于l
                if (sdsavail(s) < l) {
                    // 重新为s分配空间
                    s = sdsMakeRoomFor(s,l);
                }
                // 然后将l个str的字符拷贝到s+i的地址后面
                // i其实就是原来s的字符个数
                memcpy(s+i,str,l);
                // 更新s的长度信息
                // 调用下面这个函数告诉s新增加了多少个字符
                // 加上原来的个数就是总个数了
                sdsinclen(s,l);
                // i的信息也得更新,如果还有要格式化的字符的话
                // 就得从s+i+l的后面拷贝内容了
                i += l;
                break;
            // 如果是'i'或'I'(格式化有符号整形)
            case 'i':
            case 'I':
                // 小写'i'
                if (next == 'i')
                    // va_arg()是宏不是函数,它获取ap指向的第一个内容
                    // 已经知道了首地址,并知道所占字节数,便知道这段空间的内容了
                    // 并将ap偏移sizeof(int)个字节,使其指向下一个变参
                    num = va_arg(ap,int);
                else
                    // 大写的'I',ap就偏移sizeof(long long)个字节
                    num = va_arg(ap,long long);
                {
                    // 声明一个变量用来存放将整数转换为字符串的内容
                    char buf[SDS_LLSTR_SIZE];
                    // 调用下面这个函数将整数转换为字符串并返回个数
                    // 原理前面章节有讲
                    l = sdsll2str(buf,num);
                    // 然后判断s的可用空间是否小于l
                    if (sdsavail(s) < l) {
                        // 小于就重新为s申请空间
                        s = sdsMakeRoomFor(s,l);
                    }
                    // 最后将l个buf里的字符个数拷贝到s+i地址后面
                    memcpy(s+i,buf,l);
                    // 更新s的长度信息
                    sdsinclen(s,l);
                    i += l;
                }
                break;
            // 如果是'u'或'U'(格式化无符号整型)
            case 'u':
            case 'U':
                // 如果是小写'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;
            // 如果next并不是's S i I u U'
            default: /* Handle %% and generally %<unknown>. */
                // 直接将next接到s的后面
                // 这里就说明了为什么s必须要有一字节的可用空间
                s[i++] = next;
                sdsinclen(s,1);
                break;
            }
            break;
        // 如果*f不是'%'
        default:
            // 直接将*f接到s后面
            s[i++] = *f;
            sdsinclen(s,1);
            break;
        }
        // 指针指向下个地址,遍历下一个字符
        f++;
    }
    // 格式化完成,释放ap指针
    va_end(ap);

    /* Add null-term */
    // 最后一个字节存空字符
    s[i] = '\0';
    return s;
}

       第二十七个。字符剪切。该函数的作用是将指定的字符从字符串中剪切掉,作者注释里的例子很清楚的描述了。

// 作者注释
/* Remove the part of the string from left and from right composed just of
 * contiguous characters found in 'cset', that is a null terminted C string.
 *
 * After the call, the modified sds string is no longer valid and all the
 * references must be substituted with the new pointer returned by the call.
 *
 * Example:
 *
 * s = sdsnew("AA...AA.a.aa.aHelloWorld     :::");
 * s = sdstrim(s,"Aa. :");
 * printf("%s\n", s);
 *
 * Output will be just "Hello World".
 */

// s就是需要处理的字符串,cset就是要剪切的字符合集
// 注意!该函数并不是你想剪切什么就剪切什么
// 它只是将你想留下的某段连续的字符的前后部分给剪切掉!
// 这个函数虽然没多少行,但是乍看一下没看出它的原理,不过看多几遍就懂了
sds sdstrim(sds s, const char *cset) {
    char *start, *end, *sp, *ep;
    size_t len;

    // 指向s的首地址
    sp = start = s;
    // 指向s的尾地址,sdslen(s)就是字符串的个数,-1是因为数组下表是从0开始的
    // 比如有个数组n[2],它有两个元素,第二个元素是n[1],要指向第二个元素的地址
    // 当然是('该数组的首地址'+2-1)
    ep = end = s+sdslen(s)-1;
    // 该循环的最终目的是为了将sp指向你想留下的那段连续的字符串的首地址
    // strchr()这个标准库函数是判断*sp在cset中首次出现的位置,如果不存在,返回NULL
    // 如果不理解,就拿作者注释中的例子来一步一步执行下代码就清晰了
    // 其实大概过程就是遍历*sp在cset中是否存在,若存在,sp就指向下一位
    // 若不存在,sp就指向了那个不存在于cset中的字符的地址,循环终止
    while(sp <= end && strchr(cset, *sp)) sp++;
    // 该循环的最终目的是为了将ep指向你想留下的那段字符串的尾地址
    // 原理和上面那个循环一样,只不过倒过来了
    // 注意ep的地址要高于sp,不然就没意义了,我们最多就把整个字符串都剪切掉,这时ep==sp
    while(ep > sp && strchr(cset, *ep)) ep--;
    // 计算sp与ep之间的间隔
    // 如果sp的地址高于ep,表示剪切整个字符串,所以len为0
    // 反之len就为ep这个高地址减去sp这个低地址
    // 指针相减表示它们之间的偏移量的个数
    // 如char s[2]-s[0]和s[1]之间的偏移量是(1-0)*sizeof(char)
    // len表示你要留下的那段字符串的个数,+1还是因为数组下表从0开始
    // 假如有3个字符,sp、ep分别指向第一个和第三个字符的地址
    // 它们相减就是(2-0),+1就是该数组的长度了
    len = (sp > ep) ? 0 : ((ep-sp)+1);
    // memmove()这个标准库函数的作用是将sp中的内容拷贝len个字节到s中
    // 如果s的地址和sp的地址一样,就不用拷贝
    if (s != sp) memmove(s, sp, len);
    // 将最后一个字符的下一位致为空(为了和char*字符串通用)
    s[len] = '\0';
    // 设置s的长度信息,该函数原理前面章节有讲
    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");
 * // 1是指开头第一个字符,-1是指末尾最后一个(而s的最后一个字符是空,所以d没被裁剪掉)
 * sdsrange(s,1,-1); => "ello World"
 */

// 函数内部的逻辑理解起来有点混乱...
// 还是通过传入不同的start和end参数来试试具体裁剪结果吧
void sdsrange(sds s, int start, int end) {
    size_t newlen, len = sdslen(s);

    // s的长度为0,就没必要裁剪了,终止整个函数
    if (len == 0) return;
    // start可以为负数
    if (start < 0) {
        // 为负数,将start重新定位为len+start
        start = len+start;
        // 若还为负,说明超出了范围,就裁剪开头的第0个,也就是一个都不裁剪
        if (start < 0) start = 0;
    }
    // end的处理方式和start的一样
    // 用负数就表示从末尾开始数,-1为末尾的空字符,-2就是空字符的前一个
    // 依次类推,当然不能超过整个字符串的长度
    if (end < 0) {
        end = len+end;
        if (end < 0) end = 0;
    }
    // 通过start和end的位置计算裁剪后的字符串的个数
    // 如果start的位置比end的位置还大,就表示直接将整个裁剪掉,newlen就为0了
    // 反之,newlen就为(end-start)+1.(就是这句理解起来比较混乱...)
    // 先不管了
    newlen = (start > end) ? 0 : (end-start)+1;
    // 根据newlen是否为0来进一步处理
    if (newlen != 0) {
        // 不为0
        if (start >= (signed)len) {
            // 且start>=len,说明位置不合理
            // 直接将剩余的字符个数也就是newlen置为0
            newlen = 0;
        } else if (end >= (signed)len) {
            // 且end>=len,说明end的位置也不合理
            // end就被置为len-1,这样其实也是不裁剪末尾的字符
            end = len-1;
            // 混乱的这句...
            newlen = (start > end) ? 0 : (end-start)+1;
        }
    } else {
        // newlen为0,说明裁剪后的字符个数为0,这样start也置为0
        // 因为start要作为指针的偏移个数
        start = 0;
    }
    // start和newlen都不为0,说明进行了裁剪,就将裁剪后的字符拷贝到s
    // 通过memmove(),将s+start指向的字符串的newlen个字节拷贝给s
    if (start && newlen) memmove(s, s+start, newlen);
    // 结尾符
    s[newlen] = 0;
    // 设置s的字符长度信息
    sdssetlen(s,newlen);
}

       第二十九个。该函数的作用是将字符串中的大写字符改为小写。

// 作者注释
/* Apply tolower() to every character of the sds string 's'. */

void sdstolower(sds s) {
    // 获取s的字符个数,该函数原理前面章节有讲
    int len = sdslen(s), j;

    // 通过循环遍历每个字符,然后调用标准库函数tolower()将字符转为小写
    for (j = 0; j < len; j++) s[j] = tolower(s[j]);
}

       第三十个。该函数作用与第二十九个函数的相反,就是将字符串中的每个字符转为大写

// 作者注释
/* Apply toupper() to every character of the sds string 's'. */

void sdstoupper(sds s) {
    int len = sdslen(s), j;

    // toupper()也是标准库函数
    for (j = 0; j < len; j++) s[j] = toupper(s[j]);
}

       第三十一个。

// 作者注释
/* Compare two sds strings s1 and s2 with memcmp().
 *
 * Return value:
 *
 *     positive if s1 > s2.
 *     negative if s1 < s2.
 *     0 if s1 and s2 are exactly the same binary string.
 *
 * If two strings share exactly the same prefix, but one of the two has
 * additional characters, the longer string is considered to be greater than
 * the smaller one. */

// 
int sdscmp(const sds s1, const sds s2) {
    size_t l1, l2, minlen;
    int cmp;

    // 通过sdslen()获取字符串的长度,该函数原理前面章节有讲
    l1 = sdslen(s1);
    l2 = sdslen(s2);
    // 获取较小的那串字符的长度
    minlen = (l1 < l2) ? l1 : l2;
    // 通过memcmp()这个标准库函数来比较s1和s2的前minlen个字节的大小
    // 如果s1>s2,返回正数,<返回负数,=返回0
    cmp = memcmp(s1,s2,minlen);
    // 如果cmp为0,说明s1和s2的前minlen个字节是想等的
    // l1-l2如果为正,说明s1比s2长,为负,说明s2长,为0,说明一样
    if (cmp == 0) return l1-l2;
    // cmp不为0,cmp就代表了s1和s2的结果
    return cmp;
}

       这节就到这里吧。:)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值