首页 > 代码库 > 走进C标准库(8)——"string.h"中函数的实现相关字符串操作函数

走进C标准库(8)——"string.h"中函数的实现相关字符串操作函数

我的strcat:

 1 char *strcat(char *dest,char *src)
 2 {
 3     char * reval = dest;
 4     while(*dest)
 5         dest++;
 6     while(*src)
 7         *dest++ = *src++ ;
 8     *dest = *src;
 9     return reval;
10 }

MSVC:

 1 char * __cdecl strcat (
 2         char * dst,
 3         const char * src
 4         )
 5 {
 6         char * cp = dst;
 7 
 8         while( *cp )
 9                 cp++;                   /* find end of dst */
10 
11         while( *cp++ = *src++ ) ;       /* Copy src to end of dst */
12 
13         return( dst );                  /* return dst */
14 
15 }

在while( *cp++ = *src++ )中,条件的值为赋值语句的返回值即*cp被赋的值,也就是此时*src的值。则,当*src为0时,将其赋给*cp后完成赋值。非常简洁。

该函数的前提条件:src和dest所指内存区域不可以重叠且dest必须有足够的空间来容纳src的字符串。

 

我的strncat:

 1 char *strncat(char *dest,char *src,int n)
 2 {
 3     char * reval = dest;
 4     while(*dest)
 5         dest++;
 6     while(n-- && (*dest++ = *src++));
 7     if(n < 0)
 8         *dest = 0;
 9     return reval;
10 }

MSVC:

 1 char * __cdecl strncat (
 2         char * front,
 3         const char * back,
 4         size_t count
 5         )
 6 {
 7         char *start = front;
 8 
 9         while (*front++)
10                 ;
11         front--;
12 
13         while (count--)
14                 if (!(*front++ = *back++))
15                         return(start);
16 
17         *front = \0;
18         return(start);
19 }

 

我的strchr:

1 char *strchr(const char *s,char c){
2     while(*s)
3     {
4         if(*s == c)
5             return s;
6         s++;
7     }
8     return NULL;
9 }

MSVC:

 1 char * __cdecl strchr (
 2         const char * string,
 3         int ch
 4         )
 5 {
 6         while (*string && *string != (char)ch)
 7                 string++;
 8 
 9         if (*string == (char)ch)
10                 return((char *)string);
11         return(NULL);
12 }

 

我的strcmp:

1 int strcmp(const char *s1,const char * s2){
2     while(*s1 == *s2 && *s1)
3     {
4         ++s1;
5         ++s2;
6     }
7     return *(unsigned char *)s1 - *(unsigned char *)s2;
8 }

MSVC:

 1 int __cdecl strcmp (
 2         const char * src,
 3         const char * dst
 4         )
 5 {
 6         int ret = 0 ;
 7 
 8         while( ! (ret = *(unsigned char *)src - *(unsigned char *)dst) && *dst)
 9                 ++src, ++dst;
10 
11         if ( ret < 0 )
12                 ret = -1 ;
13         else if ( ret > 0 )
14                 ret = 1 ;
15 
16         return( ret );
17 }

strcmp返回值不必为1和-1的。使用unsigned char 因为有符号数可能会导致比较大小错误。

 

我的strcpy:

1 char *strcpy(char *dest,const char *src){
2     char * reval = dest;
3     while(*dest++ = *src++);
4     return reval;
5 }

MSVC:

1 char * __cdecl strcpy(char * dst, const char * src)
2 {
3         char * cp = dst;
4 
5         while( *cp++ = *src++ )
6                 ;               /* Copy src over dst */
7 
8         return( dst );
9 }

 

我的strncpy:

 1 char *strncpy(char *dest, const char *src, int n){
 2     char * reval = dest;
 3     while(n--){
 4         if(*src)
 5             *dest++ = *src++;
 6         else
 7             *dest++ = 0;
 8     }
 9     return reval;
10 }

MSVC:

 1 char * __cdecl strncpy (
 2         char * dest,
 3         const char * source,
 4         size_t count
 5         )
 6 {
 7         char *start = dest;
 8 
 9         while (count && (*dest++ = *source++))    /* copy string */
10                 count--;
11 
12         if (count)                              /* pad out with zeroes */
13                 while (--count)
14                         *dest++ = \0;
15 
16         return(start);
17 }

 

我的strcspn:

 1 int strcspn(const char *s1,const char *s2){
 2     const char *cp;
 3     int reval = 0;
 4     for(; *s1; s1++){
 5         for(cp = s2; *cp; cp++){
 6             if(*s1 == *cp)
 7                 return reval;
 8         }
 9         ++reval;
10     }
11     return reval;
12 }

MSVC:

 1 size_t __cdecl strcspn (
 2         const char * string,
 3         const char * control
 4         )
 5 {
 6         const unsigned char *str = string;
 7         const unsigned char *ctrl = control;
 8 
 9         unsigned char map[32];
10         int count;
11 
12         /* Clear out bit map */
13         for (count=0; count<32; count++)
14                 map[count] = 0;
15 
16         /* Set bits in control map */
17         while (*ctrl)
18         {
19                 map[*ctrl >> 3] |= (1 << (*ctrl & 7));
20                 ctrl++;
21         }
22 
23         /* 1st char in control map stops search */
24         count=0;
25         map[0] |= 1;    /* null chars not considered */
26         while (!(map[*str >> 3] & (1 << (*str & 7))))
27         {
28                 count++;
29                 str++;
30         }
31         return(count);
32 }

函数说明:strcspn()从参数s 字符串的开头计算连续的字符, 而这些字符都完全不在参数reject 所指的字符串中. 简单地说, 若strcspn()返回的数值为n, 则代表字符串s 开头连续有n 个字符都不含字符串reject 内的字符。

返回值:返回字符串s 开头连续不含字符串reject 内的字符数目。

我的实现和《C标准库》书中基本相同,不需要任何的额外存储空间,但是使用两层的循环,花费较多的时间。

该函数的实质其实是判断s1中的每一个字符,是否在s2字符串中,对于这种判断是否在其中的问题,经常会采用映射表的方式来缩减查找时间,典型实现就是布隆过滤器。

此处,MSVC的实现就是采用了映射表的方式。

因为ASCII码有256个,所以需要256bit来作为映射标记。一个字节是8bit,所以需要32个字节。所以在代码中有 unsigned char map[32]的定义。

那么,我们就需要将8bit,分别映射出一个map的下标和bit中的位位置。

map的下表需要使用5bit(32),而bit中的位位置使用剩余的3bit(8)来映射。

通过*ctrl >> 3取到高5位到0-31的映射,通过1 << (*ctrl & 7)取得到1字节中某一位的标记。

完成控制字符的映射表建立,就能用o(1)的时间完成某个字符的查找了。

 

strerror

功  能: 返回指向错误信息字符串的指针 

例如:

 1 #include <stdio.h>
 2 #include <errno.h>
 3 
 4 int main(void)
 5 {
 6    char *buffer;
 7    buffer = strerror(errno);
 8    printf("Error: %s\n", buffer);
 9    return 0;
10 }

此段代码strerror指向的内存中的字符串为No Error

 

我的strlen:

1 int strlen(const char *s){
2     const char *cp = s;
3     while(*cp){
4         cp++;
5     }
6     return (cp - s);
7 }

MSVC:

 1 size_t __cdecl strlen (
 2         const char * str
 3         )
 4 {
 5         const char *eos = str;
 6 
 7         while( *eos++ ) ;
 8 
 9         return( (int)(eos - str - 1) );
10 }

返回值应该不需要强制类型转换,因为指针相减返回值是int。当然,加上显式转换则更加明确。

ptrdiff_t
This is the type returned by the subtraction operation between two pointers. This is a signed integral type, and as such can be casted to compatible fundamental data types.

 

strpbrk和strspn的实现和strcspn相同

 

我的strrchr:

 1 char *strrchr(const char *str, const char c){
 2     const char *cp = str;
 3     if(*str == 0)
 4         return NULL;
 5     while(*str)
 6         str++;
 7     while(*cp++){
 8         if(*--str == c)
 9             return str;
10     }
11     return NULL;
12 }

MSVC:

 1 char * __cdecl strrchr (
 2         const char * string,
 3         int ch
 4         )
 5 {
 6         char *start = (char *)string;
 7 
 8         while (*string++)                       /* find end of string */
 9                 ;
10                                                 /* search towards front */
11         while (--string != start && *string != (char)ch)
12                 ;
13 
14         if (*string == (char)ch)                /* char found ? */
15                 return( (char *)string );
16 
17         return(NULL);
18 }

确实只需要初始位置的拷贝,不需要用拷贝来计数。

 

strtok,没想好如何实现比较合适。

MSVC:

 1 char * __cdecl strtok (
 2         char * string,
 3         const char * control
 4         )
 5 {
 6         unsigned char *str;
 7         const unsigned char *ctrl = control;
 8 
 9         unsigned char map[32];
10         int count;
11 
12         static char *nextoken;
13 
14         /* Clear control map */
15         for (count = 0; count < 32; count++)
16                 map[count] = 0;
17 
18         /* Set bits in delimiter table */
19         do {
20                 map[*ctrl >> 3] |= (1 << (*ctrl & 7));
21         } while (*ctrl++);
22 
23         /* Initialize str. If string is NULL, set str to the saved
24          * pointer (i.e., continue breaking tokens out of the string
25          * from the last strtok call) */
26         if (string)
27                 str = string;
28         else
29                 str = nextoken;
30 
31         /* Find beginning of token (skip over leading delimiters). Note that
32          * there is no token iff this loop sets str to point to the terminal
33          * null (*str == ‘\0‘) */
34         while ( (map[*str >> 3] & (1 << (*str & 7))) && *str )
35                 str++;
36 
37         string = str;
38 
39         /* Find the end of the token. If it is not the end of the string,
40          * put a null there. */
41         for ( ; *str ; str++ )
42                 if ( map[*str >> 3] & (1 << (*str & 7)) ) {
43                         *str++ = \0;
44                         break;
45                 }
46 
47         /* Update nextoken (or the corresponding field in the per-thread data
48          * structure */
49         nextoken = str;
50 
51         /* Determine if a token has been found. */
52         if ( string == str )
53                 return NULL;
54         else
55                 return string;
56 }

用一个static变量来记录当前分割到的位置,它是线程不安全的,多次调用也会使它失效。

 

strcoll使用当前的区域设置来比较字符串,strxfrm使用当前的区域设置来转换字符串。当前区域设置由LL_COLLATE宏指定。它们均调用带有区域设置参数的内部版本strcoll_l和strxfrm_l来完成实际的工作。