【C/C++ 字符串】探索C语言之字符串分割函数:strtok和strsep的区别

简介: 【C/C++ 字符串】探索C语言之字符串分割函数:strtok和strsep的区别

概述

在c语言中,字符串分割函数主要有两种:一是strtok函数,另一个就是strsep函数。
下面我们对这两个函数作一个详细解释说明。

对比项 strtok strsep
功能 根据一组分隔符对字符串进行分割 根据一组分隔符对字符串进行分割
是否修改原始字符串
线程安全性 不是线程安全的 是线程安全的
处理相邻分隔符的方式 如果两个分隔符相邻,它会将它们视为一个 如果两个分隔符相邻,它会将它们视为两个,并在它们之间返回一个空字符串
返回值 返回指向分隔符之前的第一个字符的指针,同时将分隔符替换为 ‘\0’ 返回指向分隔符之前的第一个字符的指针,同时将分隔符替换为 ‘\0’
用途 用于解析简单的、格式固定的字符串,如 CSV 文件 用于解析复杂的、格式可变的字符串,如 HTTP 请求头

strsep - extract token from string(linux 下)

#include <string.h>
char *strsep(char **s, const char *delim);
//会修改数据源。可重入的,注意这里虽然改动stringp的内容,主要是不在使用static静态变量了。

作用:
stringp为指向欲分割的字符串,delim为分隔符,函数将返回分隔符前面的字符串,stringp将指向分隔符之后的字符串。
Be cautious when using this function. If you do use it, note that:
This function modifies its first argument.
This function cannot be used on constant strings.
The identity of the delimiting character is lost.


strtok, strtok_r - extract tokens from strings

#include <string.h>
char *strtok(char *str, const char *delim);
//会修改数据源。外部加锁才线程安全(strtok执行结束再解锁执行另一个strtok循环知道工作完成) char
*strtok_r(char *str, const char *delim, char **saveptr);

代码示例:*

#include <string.h>
#include <stdio.h> int main(int arg, const char *argv[]) {
  /*字符串不能为常量*/ 
  char* string = strdup("/home/yinlijun/project:/home/yinlijun:/home/someone");
  char* p;
   while((p = strsep(&string, ":")) != NULL) /*第一个参数设为二级指针,字符串中所有的第二个参数(子串)最后会被替代成‘/0’*/ 
     { 
       printf("%s/n", p);
     }
   free(string); return 0; }

相同点

对该函数的调用序列将str拆分为token,这些token是由作为定界符一部分的任何字符分隔的连续字符的序列。
首次调用时,该函数需要一个C字符串作为str的参数,该字符串的第一个字符用作扫描令牌的起始位置。
在随后的调用中,该函数需要一个空指针,并使用最后一个标记结束后的位置作为扫描的新起始位置。
为了确定token的开头和结尾,该函数首先从起始位置扫描未包含在定界符中的第一个字符(该字符成为token的开头)。
然后从token的开头开始扫描定界符中包含的第一个字符,该字符成为token的末尾。如果找到终止的空字符,扫描也会停止。
token的此结尾将自动替换为空字符(NULL),并且token的开头由函数返回。
一旦在对strtok的调用中找到了str的终止null字符,此函数的所有后续调用(将null指针作为第一个参数)都将返回null指针。
找到下一个标记的点由在下一次调用时使用的函数在内部保留(不需要特殊的库实现来避免数据争用)。
两者都会对原字符串进行修改。


不同点

  • strtok内部记录上次调用字符串的位置,所以是不可重入的,不支持多线的,其可重入版本为strtok_r。
  • strsep使用传入的参数来确定字符串的起始位置,是可重入的,也是Linux kernel推荐的函数,strtok的替代品。
  • strtok()是被标准化(C标准,并因此也通过POSIX),
    但strsep()不规范(由C或POSIX;它是GNU C库中可用的,和起源于BSD).因此,可移植的代码strtok()比strsep().更容易使用
  • strtok()允许在单个标记之间使用多个分隔符,而strsep()期望标记之间使用单个分隔符,并将相邻分隔符解释为空标记.

什么时候使用它们?

  • 使用strsep函数,当您需要处理空字段,或者在不同字段之间使用单个分隔符,并且在不介意可移植性的情况下。
  • 使用strtok_r函数,当您希望在字段之间允许多个分隔符,不需要处理空字段,并且要求代码具有良好的POSIX兼容性。
  • 尽量避免使用strtok函数,因为它是不可重入的,容易导致问题。尤其在多线程环境中,strtok的使用可能导致程序出现不稳定的行为。

您可以strsep()在需要空字段时使用,而不是在字段之间允许多个分隔符,并且在您不介意可移植性时使用.
你会使用strtok_r(),当你想允许标记之间的多个分隔符,你不想空字符(和POSIX足够的便携式你).
strtok()如果你不这样做,你只会在有人威胁你的生命时使用.而你只能用它足够长的时间让你摆脱危及生命的境地; 然后你会再次放弃使用它.它有毒; 不要使用它.编写自己的strtok_r()或者strsep()使用它会更好strtok().

为什么strtok()有毒?

strtok()如果在库函数中使用,该函数是有毒的.如果您的库函数使用strtok(),则必须清楚地记录.
那是因为:

  • 如果任何调用函数正在使用strtok()并调用也使用的函数strtok(),则会中断调用函数.
  • 如果你的函数调用任何调用的函数strtok(),那将破坏你的函数的使用strtok().
  • 如果您的程序是多线程的,那么strtok()在任何给定时间最多只能有一个线程使用- 跨越一系列strtok()调用.

此问题的根源是调用之间保存的状态,允许strtok()在停止的位置继续.除了"不要使用strtok()" 之外,没有合理的方法来解决问题.

  • strsep()如果可用,您可以使用.
  • 您可以使用POSIX(strtok_r()如果可用).
  • strtok_s()如果可用,您可以使用Microsoft .
  • 名义上,您可以使用ISO/IEC 9899:2011附件K.3.7.3.1功能strtok_s(),但其界面strtok_r()与微软的界面不同strtok_s()


目录
相关文章
|
3月前
|
存储 C语言
`scanf`是C语言中用于按格式读取标准输入的函数
`scanf`是C语言中用于按格式读取标准输入的函数,通过格式字符串解析输入并存入指定变量。需注意输入格式严格匹配,并建议检查返回值以确保读取成功,提升程序健壮性。
1017 0
|
5月前
|
安全 C语言
C语言中的字符、字符串及内存操作函数详细讲解
通过这些函数的正确使用,可以有效管理字符串和内存操作,它们是C语言编程中不可或缺的工具。
329 15
|
6月前
|
算法 Java 数据库连接
Java 与 C++ 区别深入剖析及应用实例详解
本文深入剖析了Java和C++两种编程语言的区别,从编译与执行机制、面向对象特性、数据类型与变量、内存管理、异常处理等方面进行对比,并结合游戏开发、企业级应用开发、操作系统与嵌入式开发等实际场景分析其特点。Java以跨平台性强、自动内存管理著称,适合企业级应用;C++则因高性能和对硬件的直接访问能力,在游戏引擎和嵌入式系统中占据优势。开发者可根据项目需求选择合适语言,提升开发效率与软件质量。附面试资料链接:[点此获取](https://pan.quark.cn/s/4459235fee85)。
576 0
|
6月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
183 0
|
10月前
|
人工智能 Java 程序员
一文彻底搞清楚C语言的函数
本文介绍C语言函数:函数是程序模块化的工具,由函数头和函数体组成,涵盖定义、调用、参数传递及声明等内容。值传递确保实参不受影响,函数声明增强代码可读性。君志所向,一往无前!
424 1
一文彻底搞清楚C语言的函数
|
9月前
|
安全 C++
【c++】继承(继承的定义格式、赋值兼容转换、多继承、派生类默认成员函数规则、继承与友元、继承与静态成员)
本文深入探讨了C++中的继承机制,作为面向对象编程(OOP)的核心特性之一。继承通过允许派生类扩展基类的属性和方法,极大促进了代码复用,增强了代码的可维护性和可扩展性。文章详细介绍了继承的基本概念、定义格式、继承方式(public、protected、private)、赋值兼容转换、作用域问题、默认成员函数规则、继承与友元、静态成员、多继承及菱形继承问题,并对比了继承与组合的优缺点。最后总结指出,虽然继承提高了代码灵活性和复用率,但也带来了耦合度高的问题,建议在“has-a”和“is-a”关系同时存在时优先使用组合。
484 6
|
11月前
|
存储 编译器 C语言
【C语言程序设计——函数】分数数列求和2(头歌实践教学平台习题)【合集】
函数首部:按照 C 语言语法,函数的定义首部表明这是一个自定义函数,函数名为fun,它接收一个整型参数n,用于指定要求阶乘的那个数,并且函数的返回值类型为float(在实际中如果阶乘结果数值较大,用float可能会有精度损失,也可以考虑使用double等更合适的数据类型,这里以float为例)。例如:// 函数体代码将放在这里函数体内部变量定义:在函数体中,首先需要定义一些变量来辅助完成阶乘的计算。比如需要定义一个变量(通常为float或double类型,这里假设用float。
522 3
|
10月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
6月前
|
存储 编译器 程序员
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
274 0
|
8月前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
314 12