3. strib库的实现
正如本章前文所述,strlib.h
的主要目的是对客户隐藏有关内存分配的复杂性。
使用该库时,客户不必考虑分配固定大小的字符数组,也不必担心字符数组是否超出了内存空间的界限。
所有的字符串都表示为指针,它指向从堆中通过动态分配所获得的字符内存空间。
3.1 实现转换函数
在stlib.h
的所有函数中, 实现起来最简单的就是那些和string.h
里的函数一样的函
数,如StringLength
和StringCompare
。
在这种情况下,通常的做法是让一个接口重新命名比它更低层的接口的函数。这样的函数就称为转换函数。
提供一个传递函数而不让客户使用较低层次的对应函数的目的是为了减少客户在概念上的复杂性。
定义了strlib.h
,就可以在准备使用较低层的接口之前不需要学习低层接口。
转换函数的实现极为简单。strlib.c
版本的StringLength
和StringCompare
如下所示:
int StringLength2(string s) {
return strlen(s);
}
int StringCompare(string s1, string s2) {
return strcmp(s1, s2);
}
3.2 strlib分配函数的实现
strlib.h
接口里大多数函数的主要特征是它们动态地为新创建的字符串分配内存。
为了集中这一阶段的操作,strlib.c
程序定义了一个专用函数Createstring(len)
,用于给长度为len
的字符串分配内存, 而且还考虑到在字符串最后为’\0’预留一个额外的字节。
CreateString
的实现可以写为:
static string CreateString(int len) {
return (string) GetBlock(len + 1);
}
可以用上述函数来写strlib
中所有需要分配内存的函数。
例如,考虑CharToString
的实现,为一个只含一个字符的字符串分配内存。具体实现如下:
string CharToString(char ch) {
string result;
result = CreateString(1);
result[0] = ch;
result[1] = '\0';
return result;
}
函数动态地分配空间来存放只有一个字符的字符串,然后通过赋入特定的字符和终止空字符来初始化该内存。
看一个更实质性的函数,如Concat
。该实现先为连接后的字符串分配空间,再依次从每个字符串复制字符,如下所示:
string Concat(string s1, string s2) {
string s;
int len1, len2, i;
len1 = strlen(s1);
len2 = strlen(s2);
s = CreateString(len1+len2);
for (i=0; i<len1; i++) s[i] = s1[i];
for (i=0; i<len2; i++) s[i+len1] = s2[i];
s[len1+len2] = '\0';
return s;
}
尽管这个实现能工作,但它并没有利用较低层库中的函数。
通常,ANSI字符串库中函数的设计应使得它们尽可能利用所运行的计算机的硬件特征而有效地工作。
如果在实现中使用了这些函数,那么产生的代码常常会比采用显式字符串操作的程序更有效。
所以,将strcpy
作为实现的一部分使用是很有意义的,程序如下:
string Concat2(string s1, string s2) {
string s;
int len1, len2;
len1 = strlen(s1);
len2 = strlen(s2);
s = CreateString(len1+len2);
strcpy(s, s1);
strcpy(s+len1, s2);
return s;
}
其中,
strcpy(s+len1, s2);
关于该行代码的理解,需记住,给指针加一个整数就会使指针向前推移指定数目个元素。
在本例中,由于操作的是字符,所以目标地址只是从s
后第len1
个字符开始的数组地址,也就是s2
的副本所在。
在这里,调用strcpy(s+len 1, s2)
和调用strcat(s, s2)
的效果是相同的。
但strcpy
的形式更有效,因为它避免了搜寻到字符串s
的末尾。
参考
《C语言的科学和艺术》 —— 14 再论字符串