IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C语言源码剖析与实现——strtok()系列函数实现 -> 正文阅读

[C++知识库]C语言源码剖析与实现——strtok()系列函数实现

源码剖析与实践

strtok() 源代码实现

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

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

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 == '\0') {
        _strtok = NULL;
        return (NULL);
    }
    send = strpbrk(sbegin, ct);
    if (send && *send != '\0')
        *send++ = '\0';
    _strtok = send;
    return (sbegin);
}

源代码用的另外几个函数 strspn() 、 strpbrk() 、strcspn()

strspn()

strspn的使用

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

/* strspn example */
#include <stdio.h>
#include <string.h>

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)!='\0'){	//创建set
        cset[*s2] = 1;
        ++s2;
    }
    int idx = 0;
    while(s1[idx] != '\0'){//得到正确的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)!='\0'){	//创建set
        cset[*s2] = 1;
        ++s2;
    }
    int len = 0;
    while(s1[idx] != '\0'){//得到正确的idx位置
        if(!cset[s1[len]])
        	len++;
        else
            break;
    }
    return len;
}

strpbrk()

strpbrk的使用

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

/* strpbrk example */
#include <stdio.h>
#include <string.h>

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) != '\0') {                      //创建set,%32决定放入哪个盒子中,/32决定放入8位的哪个位
        uchar t = (uchar) *s2++;
        cset[get_pos(t)] |= 1 << (t / 32);       //由于最大值255所以t/32为0~31
    }
    while ((*s1) != '\0') {
        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 <stdio.h>
#include <string.h>

void func() {
    char chSrc[] = "Can I help you";//注意不能直接用常量,需要有内存
    char *save = NULL;          //用于缓存
    char *pchSrc = chSrc;
    while (NULL != (pchSrc = strtok_s(pchSrc, " ", &save))) {
        printf("\npchSrc: %s\nsave: %s\n", 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 == '\0') {
        *save_ptr = s;
        return NULL;
    }
    /* Scan leading delimiters.  */
    s += strspn(s, delim);
    if (*s == '\0') {
        *save_ptr = s;
        return NULL;
    }
    /* Find the end of the token.  */
    end = s + strcspn(s, delim);
    if (*end == '\0') {
        *save_ptr = end;
        return s;
    }
    /* Terminate the token and make *SAVE_PTR point past it.  */
    *end = '\0';
    *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) != '\0') {       //更新set
        uchar t = (uchar) *delimiters++;
        cset[get_pos(t)] |= 1 << (t / 32);//由于最大值255所以t/32为0~7
    }
    //让sbegin指向不在set中的位置
    while (*sbegin != '\0' && (cset[get_pos(*sbegin)] & (1 << (((uchar) *sbegin) / 32)))) {
        ++sbegin;
    }
    if (*sbegin == '\0') {
        ssave = NULL;
        return NULL;
    }
    int idx = 0;
    //相当于strcspn的功能了
    while (sbegin[idx] != '\0' && !(cset[get_pos(sbegin[idx])] & (1 << ((uchar) sbegin[idx] / 32)))) {
        ++idx;
    }
    send = sbegin + idx;
    if (*send != '\0')
        *send++ = '\0';                   //画上终止符
    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("%s\n", pch);
        pch = strtok(NULL, " ,.-");
    }
    return 0;
}

测试结果:

很好非常perfect!!!

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-11-09 19:17:43  更:2021-11-09 19:18:25 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 5:49:47-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码