感觉分的太散了,每篇才几个函数,导致章节太多…不过还是先记录完再纠结这个问题吧。
第二十六个。该函数的作用和第二十五的差不多,只不过速度会快一点,因为函数内部没有使用C标准库中的类sprintf()
函数(作者说这类函数的速度很慢…),但是处理的格式没那么全,如浮点格式的%f
,且有些格式是作者自创的,如%U
64位无符号整形格式。这些说明在作者注释里都有提到。关于变参问题,我搜索了解了下,发现这篇百科解释的挺清楚的,虽然具体内部原理还不清楚,但相关概念还是知道了,这样有易于理解代码。
// 作者注释
/* 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;
}
这节就到这里吧。:)