栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C语言源码剖析与实现——strtok()系列函数实现

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C语言源码剖析与实现——strtok()系列函数实现

文章目录
  • 源码剖析与实践
    • strtok() 源代码实现
      • 源代码用的另外几个函数 strspn() 、 strpbrk() 、strcspn()
        • strspn()
        • strcspn()
        • strpbrk()
    • strtok_r() 源代码实现
    • (不依赖其他函数库)完全自己实现strtok()
      • 设计方案
      • 代码实现
      • 性能分析
      • 测试用例

源码剖析与实践 strtok() 源代码实现

由于是用的static实现的全局变量存储地址,所以只需要传一次,后面传NULL,即可对之前的字符串进行操作。

  • 但同样也造成了一个问题:如果有两个线程都用了这一个函数,由于是全局变量,所以他们会共享这个变量,如果两个线程处理的是不同的两个字符串,而它们的地址存储都是用的同一个变量,这会导致原本应该处理的是本身这个线程所处理的字符串而反而去去处理了另一个线程的字符串了。
    这就是所谓的线程不安全。
#include

static char *_strtok = NULL; //用全局变量缓存上一次的结果

char *
strtok(char *s, char const *ct) {
    char *sbegin, *send;

    sbegin = s ? s : _strtok;
    if (!sbegin) {
        return NULL;
    }
    sbegin += strspn(sbegin, ct);
    if (*sbegin == '') {
        _strtok = NULL;
        return (NULL);
    }
    send = strpbrk(sbegin, ct);
    if (send && *send != '')
        *send++ = '';
    _strtok = send;
    return (sbegin);
}
源代码用的另外几个函数 strspn() 、 strpbrk() 、strcspn() strspn()

strspn的使用

第二个参数形成一个集合,返回第一个字符串参数中不在集合里的第一个字符下标位置。

#include 
#include 

int main ()
{
  int i;
  char strtext[] = "129th";
  char cset[] = "1234567890";

  i = strspn (strtext,cset);
  printf ("The initial number has %d digits.n",i);
  return 0;
}

strspn实现(个人实现版本)

int strspn(const char* s1,const char* s2){
    if(s1==NULL || s2==NULL)//处理特殊情况
        return 0;
    bool cset[256]={0};
    while((*s2)!=''){	//创建set
        cset[*s2] = 1;
        ++s2;
    }
    int idx = 0;
    while(s1[idx] != ''){//得到正确的idx位置
        if(cset[s1[idx]])
        	idx++;
        else
            break;
    }
    return idx;
}
strcspn()

原型:

size_t strcspn(const char *str1, const char *str2)

作用和strspn类似,只不过多了一个c意为constant,连续的。

所以它是找出从str1开始,连续的不在str2字符集合中的长度。

代码实现(个人功能实现)

int strspn(const char* s1,const char* s2){
    if(s1==NULL || s2==NULL)//处理特殊情况
        return 0;
    bool cset[256]={0};
    while((*s2)!=''){	//创建set
        cset[*s2] = 1;
        ++s2;
    }
    int len = 0;
    while(s1[idx] != ''){//得到正确的idx位置
        if(!cset[s1[len]])
        	len++;
        else
            break;
    }
    return len;
}
strpbrk()

strpbrk的使用

同样也是先建立字符的set,返回第一个字符串中在这个集合内的字符的第一个位置的地址(指针)。

#include 
#include 

int main ()
{
  char str[] = "This is a sample string";
  char key[] = "aeiou";
  char * pch;
  printf ("Vowels in '%s': ",str);
  pch = strpbrk (str, key);
  while (pch != NULL)
  {
    printf ("%c " , *pch);
    pch = strpbrk (pch+1,key);
  }
  printf ("n");
  return 0;
}

strpbrk的实现(个人实现版本_空间节省版)

为了节省空间我这边将每一个位都利用了,大家如果看不懂,可以用256个bool(一个字节)变量之间存。

typedef unsigned char uchar;

inline int get_pos(uchar x) {
    return x % 32;
}

char *strpbrk(const char *s1, const char *s2) {
    if (s1 == NULL || s2 == NULL)
        return NULL;
    uchar cset[32] = {0};                        //用32个unsigned char对每个位进行bool运算可以更节省内存
    while ((*s2) != '') {                      //创建set,%32决定放入哪个盒子中,/32决定放入8位的哪个位
        uchar t = (uchar) *s2++;
        cset[get_pos(t)] |= 1 << (t / 32);       //由于最大值255所以t/32为0~31
    }
    while ((*s1) != '') {
        uchar t = (uchar) *s1;
        if (cset[get_pos(t)] & (1 << (t / 32))) {//bitmap的查找方式
            return (char *) s1;
        } else {
            ++s1;
        }
    }
    return NULL;
}
strtok_r() 源代码实现

函数原型:

char * strtok_r (char *s, const char *delim, char **save_ptr);

由于没有用到static全局变量,所以需要从外面传入一个指针变量用于存储当前操作的字符串。

由于用到的是外部的变量进行缓存,不会产生线程安全问题!

以下为使用测试:

#include 
#include 

void func() {
    char chSrc[] = "Can I help you";//注意不能直接用常量,需要有内存
    char *save = NULL;          //用于缓存
    char *pchSrc = chSrc;
    while (NULL != (pchSrc = strtok_s(pchSrc, " ", &save))) {
        printf("npchSrc: %snsave: %sn", pchSrc, save);
        pchSrc = NULL;
    }
}

int main() {
    func();
    return 0;
}

strtok_r() 源代码:

char *
strtok_r(char *s, const char *delim, char **save_ptr) {
    char *end;
    if (s == NULL)
        s = *save_ptr;
    if (*s == '') {
        *save_ptr = s;
        return NULL;
    }
    
    s += strspn(s, delim);
    if (*s == '') {
        *save_ptr = s;
        return NULL;
    }
    
    end = s + strcspn(s, delim);
    if (*end == '') {
        *save_ptr = end;
        return s;
    }
    
    *end = '';
    *save_ptr = end + 1;
    return s;
}
(不依赖其他函数库)完全自己实现strtok() 设计方案

设计方案:

  1. 功能:将字符串根据分隔符分割
  2. 原理:全局变量状态实现缓存
  3. 关键:由于缺少strspn()和strpbrk(),根据自己实现了strspn()、strcspn()和strpbrk()后,发现就是建立集合进行判断移动指针的过程。
  • 为了节省空间我这边将每一个位都利用了,大家如果看不懂,可以用256个bool(一个字节)变量之间存。

还是怕有人看不懂,问为什么需要256个位置进行映射,为什么需要uchar?在这里进行解答:因为一个字符为1个字节,所以如果不考虑符号,则最多表示0~255,由于我们是用数组下标对字符的数值进行映射,所以需要为非负数,故转uchar,而我typedef为uchar只是单纯的觉得unsigned char太长了而已藍

代码实现
typedef unsigned char uchar;

inline int get_pos(uchar x) {
    return x % 32;
}

char *strtok(char *src, const char *delimiters) {
    char *sbegin, *send;
    static char *ssave = NULL;
    sbegin = src ? src : ssave;           //如果src为NULL就继续上一次的缓存
    uchar cset[32] = {0};                 //用32个unsigned char对每个位进行bool运算可以更节省内存
    while ((*delimiters) != '') {       //更新set
        uchar t = (uchar) *delimiters++;
        cset[get_pos(t)] |= 1 << (t / 32);//由于最大值255所以t/32为0~7
    }
    //让sbegin指向不在set中的位置
    while (*sbegin != '' && (cset[get_pos(*sbegin)] & (1 << (((uchar) *sbegin) / 32)))) {
        ++sbegin;
    }
    if (*sbegin == '') {
        ssave = NULL;
        return NULL;
    }
    int idx = 0;
    //相当于strcspn的功能了
    while (sbegin[idx] != '' && !(cset[get_pos(sbegin[idx])] & (1 << ((uchar) sbegin[idx] / 32)))) {
        ++idx;
    }
    send = sbegin + idx;
    if (*send != '')
        *send++ = '';                   //画上终止符
    ssave = send;                         //更新下一次处理的缓存位置
    return sbegin;
}
性能分析

问题规模N为字符串的长度,由于设计strtok采用的是集合存储方式,而不是暴力枚举,所以时间复杂度占优为 O(n) !而用于存储集合元素的数组也通过位运算实现了空间优化,空间不随问题规模改变,所以 O(1) 的空间复杂度。

时间复杂度: O ( N ) O(N) O(N)

空间复杂度: O ( 1 ) O(1) O(1)

测试用例

测试的main函数

测试的说明:以 ' ' ',' , '.' '-' 为分隔符对整个字符串进行分割。

int main() {
    char str[] = "- This 3234d s3242- -d-, a sample string.";
    char *pch;
    printf("Splitting string "%s" into tokens:n", str);
    pch = strtok(str, " ,.-");
    while (pch != NULL) {
        printf("%sn", pch);
        pch = strtok(NULL, " ,.-");
    }
    return 0;
}

测试结果:

很好非常perfect!!!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/443973.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号