string.h中库函数的实现

 

Task:

编写一个程序,将某文件名,如F:/testsue/input.txt,更改为F:/testuse/input_cutted.txt

 

这是我在编写一个分词程序时候遇到的问题。初看起来,该问题非常之简单,首先想到的是使用string.h中的几个库函数来实现,可能你会编码如下(不使用临时数组)

 

 

先看下strlen的实现(参考自P. J. Plauger C标准库》):

 

 

 

可以看出strlen是通过对字符串进行遍历来计算长度的;在以上算法中,strlen使用了两次,因此程序分别对tmp数组和原path数组进行了一次遍历;此后程序又使用while_1从后向前对path进行遍历并在此过程中进行赋值操作;在while_1结束后,程序继续使用while_2同时对pathtmp进行遍历。在这两次while遍历中,对tmp的遍历是完全遍历的而对path的遍历至少进行了一半;连同之前两次strlen所进行的遍历,程序一共对path进行了1.5次遍历,对tmp进行了2遍历;

如果该字符串有很长,虽然时间复杂度仍然是O(n),但其效率不是可被接受的。如果可以在前两次strlen的同时做点什么,使后来对pathtmp进行的while遍历不发生或少发生,那么效率无疑将得到大步提升。请看以下代码:

 

 

在该代码中,手工实现了strlen等的功能;总的看来,程序对path只进行了1次遍历,对tmp只进行了1次遍历;比较第一个算法,该算法的效率无疑更好。

 

库函数的存在是为了封装某些操作使程序员不必拘泥于细节的实现。但在使用库函数时,应适当了解库函数的实现,正确的使用库函数,否则,轻则影响程序的效率,重则将导致程序崩溃。

 

 

 

 

可以看出,在strcpy中并没有NULL检查,并没有数组空间检查;因此如果错误的使用,将造成难以预料的结果。

 

string.h中,很多库函数都通过遍历字符串来实现。如上面的strlenstrcpy。又如strcat

 

因此,当我们使用string.h中的库函数时,我们应该考虑下,是否有使用的必要,使用库函数是否会导致多次遍历同一字符串从而导致效率的低下?是否有更好的办法来实现这些目的?

 

有些库函数只有知道了其实现,才能更好的理解其用法。例如下面的memset

 

 

 

  

 

memset内部使用1个字节的char,因此在使用memset时,应根据其类型来选择不同的调用方式

例如,int buff[10];调用方式是memset(buff,0,sizeof(buff)); 而不能是memset(buff,’0’,10)

又如:char buff[10];调用方式是memset(buff,'0',sizeof(buff)); 而不能是memset(buff,0,10)

特别要注意的是,若声明int buff[],则memset的使用目的只会是设置该数组的所有单元均为0,不可能将buff中所有单元设为其他数……(因为在memset中,使用了8位的char,因此,若想设置buff的每一个单元为1,则实际结果是,buff[]每一个单元的int4个字节,每个字节均为1,因此该int数据实际上是1 00000001 00000001 00000001(32位机),即是16843009

另要注意,对char的使用一定要传字符,如’1’是将char[]的每个单元初始化为11,则无法;

 

又如,strncat,我的实现为:

 

 

 

 

我觉得MARK处用得相当好,相当有创意,原来都不知道。就像上面的strcat中的for(;(*s=*s2)!='/0';++s,++s2)并不会影响性能,且括起来相当简洁,我认为相当不错。(用优先级规则怎么分析?)

 

如下面的strncpy

 

 

 

strncpychar *src中之多n个元素复制至des中,如果src中元素个数少于n,则des中相应的部分应全填充为’/0’

两点注意:

首先,如果这样写代码,那么在MARK处必须写成while(*src!='/0'&&0<n--)而不能是while(0<n--&&*src!=’/0’)。也就是说,while中的判断顺序不能反。形如while(*src!='/0'&&0<n--)时候,当src中字符数少于n时,while会在n>0之前遇到’/0’,此时进行while判断时,*src!=’/0’必为假,因此while会直接break而不会进行0<n—的判断,也就是说,n不会变化。这样就能保证在第二个while中能复制足够的’/0’;而如果使用while(0<n--&&*src!=’/0’)时,当判断出*src==’/0’时,n已经--,这样在while break时,n值比实际的值小1。在某些实现中,这可能造成相当严重的后果;

第二,在使用strncpy时,程序员同样应该保证des中有足够多的空间存放来自srcn个字符。即使src中实际上并没有n个字符要复制。当src中实际上没有n个字符要复制时,des同样会被复制进”n个字符,同样会覆盖des后的其他数据。虽然如果仅仅复制src中有的数据不会出现这样的情况。

 

strcspn:用于返回char *s1中从起始位置起,不包含char *s2中任意字符的字符段的最后一个字符的位置(索引);即是,s1中从起始位置起,开头的n个字符均不出现于s2,于是返回该n(作为索引)

我的算法是:

 

 

但《C标准库》中的代码却是:

 

 

 

 

很明显能看出,《C标准库》的算法没有我的算法的效率高,我的算法所使用的空间较多。为什么会这样?我认为是作者考虑到,如果该函数运行在内存有限的平台上,那么开辟256个空间单元的愿望不免奢侈了一些,或者说不可能完成。于是宁愿采用稍微保守一点但移植性很好的算法。不过我认为,我的算法在支持ANSI C的开发环境中,且内存限制不大的环境中,不会出现运行问题。总觉得我所使用的参考数组int pre[256]={0};有问题……或者,可以只使用127个单元而不使用256个单元吗?

与之同理的还有strpbrk.其形式为:

char *strpbrk (cosnt char*,const char*);

值得注意的是,strpbrk中,return是:return ((char*)sc1);其函数形式如下:

 

 

 

 

 

sc1声明为const char*,如果直接return sc1,虽然在strpbrk中,sc1const,但调用strpbrk的函数仍然可以使用strpbrk所得到的指针,例如改变该地址的值等。这是因为cosnt所修饰的sc1的作用范围只是strpbrk。但如果直接返回,则编译器会给出警告warning C4090: 'return' : different 'const' qualifiers。于是作者在这里使用了return ((char*)sc1)以消除该警告。但如果strpbrk的返回值定义为const char*,则在调用strpbrk的函数中,该指针不能进行赋值等操作,因为return的是一个const,那么接收的必然也是const……

注意strcspnstrspn的区别:

 

 

strspn返回的是:s1中第一个不出现在s2中的字符的位置;这与strcspn是不同的,strcspn返回的是从s1起始位置开始,最后一个不出现在s2字符集的字符的位置。这两者是不同的……不过两者的算法实在太像了……

另,在《C标准库》中,strspn的实现中,使用的第二个for是:

 

 

 

不知为何?为什么要将if(*sc2==*sc1)单独写出来?仅仅是为了直观?

 

strrchr:函数声明为char* strrchr (const char*,int);我的实现为:

 

 

 

这与《C标准库》的代码有点不同,在《C标准库》中,strrchr实现为:

 

 

 

 

为什么要将*s==’/0’的判断放在for中的if里?为什么不像我一样,将之放在for体中?我的算法不是有更好的可读性和效率吗?

想了半天才弄明白,这是因为:strrchr准许用户用于在一个字符串中搜索’/0’。如果按照我的算法,是无法搜索’/0’,于是有了《C标准库》中的strrchr

以下是测试用例:

 

 

我的算法的打印结果是:"不可以搜索’/0’"……

不得不佩服写标准库这些人的思虑,太周到了……

还有一点,注意在传递来的参数中的int ch;strrchr中要使用一个unsigned char转写,否则可能得不到正确的结果。

 

strstrchar* strstr(const char*,const char*);

该函数查找s2第一次出现在s1中的位置。若没有,则返回NULL

我的实现代码是:

  

 

一如既往的,没有进行NULL检查。但很不幸的发现,《C标准库》中进行了检查……(知道原因了,见最后……)从本质上,该算法没有任何困难之处。但《C标准库》的算法和我有很大不同……我的算法中,for(sc2=s2;(*sc1==*sc2)!='/0';sc1++,sc2++)是能正常运行的,开始我怀疑了好久……

C标准库》中的算法如下:

 

 

 

这也是我根据《C标准库》上的算法自己写的,和《C标准库》中的方法也还是有不一样的,最大的不同在于:《C标准库》中的for和我的不太一样。for如下:

 

 

 

想了半天,想不通为何他特地那样写?有什么用途吗?我没有看出来……

我将自己的for更改了一下,使用了while而不是for,如下:

 

 

 

我觉得我自己很喜欢使用while,而不太喜欢使用for。但作者使用了很多for,却很少使用while,是否是for的效率要高于while?还是有别的什么原因?我总觉得while看起来更紧凑。

对于strstr,我更喜欢这样的版本:

 

 

 

我喜欢while多于for……

另,注意,strstr将空串看做是任意串的字串,于是当s2==NULL时,返回的是s1,因此在算法的开始,程序才进行s2NULL检查,如果不进行,势必会return NULL.这样就违背题意了。

 

strtok:

最后一个函数,strtokstrtok用于按所给分隔符打印字符串。可能这是string.h中最难的字符串函数了。

以下是我最开始编写的函数。总体思想是正确的,但在个别地方出错,导致整个算法失败……

 

 

 

后来参考了《C标准库》中的实现,写出了正确的函数,如下:

 

  

 

最开始用的是(s==NULL)?(sstart=ssave):(sstart=s);后来才发现可以使用更简单的方式:sstart=s?s:ssave;真的是一个最简单的表达式就可以看出一个人的水平如何。

 

总结:

1.              如果在函数内定义了const,而该const又要返回,则在return的时候,宜将  之强制类型转换

2.              若函数形参在函数运行中不会改变,则最好能加const修饰……

3.              whilefor,谁的效率更高?

4.       总体上来讲,我还需要更加努力才是…… 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值