惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

N
News | PayPal Newsroom
云风的 BLOG
云风的 BLOG
GbyAI
GbyAI
Engineering at Meta
Engineering at Meta
B
Blog RSS Feed
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
The Register - Security
The Register - Security
L
LangChain Blog
A
About on SuperTechFans
S
Schneier on Security
博客园 - 三生石上(FineUI控件)
Stack Overflow Blog
Stack Overflow Blog
The Hacker News
The Hacker News
AWS News Blog
AWS News Blog
博客园 - 司徒正美
Scott Helme
Scott Helme
K
Kaspersky official blog
Cyberwarzone
Cyberwarzone
T
Tenable Blog
腾讯CDC
Recorded Future
Recorded Future
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
G
GRAHAM CLULEY
Security Latest
Security Latest
S
Securelist
D
Darknet – Hacking Tools, Hacker News & Cyber Security
aimingoo的专栏
aimingoo的专栏
Google DeepMind News
Google DeepMind News
V
Vulnerabilities – Threatpost
雷峰网
雷峰网
T
The Exploit Database - CXSecurity.com
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
V
V2EX
T
The Blog of Author Tim Ferriss
D
Docker
S
Security Affairs
F
Full Disclosure
Know Your Adversary
Know Your Adversary
N
News and Events Feed by Topic
N
News and Events Feed by Topic
T
Tor Project blog
Hugging Face - Blog
Hugging Face - Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Microsoft Security Blog
Microsoft Security Blog
Simon Willison's Weblog
Simon Willison's Weblog
Recent Announcements
Recent Announcements
博客园_首页
博客园 - 聂微东
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
S
Security @ Cisco Blogs

风萧古道 - 勤学苦练,年复一年

游戏服务器开发经验(五)应对复杂需求 沉浸式体验东汉末年生活 - 《真三国无双 起源》玩后感 怒其不争!致2025年HLTV的Top18-NiKo Linux家用服务器维护指南 游戏服务器开发经验(四)避免写Bug的习惯、技巧和心态 游戏服务器开发经验(三)线上维护 游戏服务器开发经验(二)避免内存泄露 游戏服务器开发经验(一)道具防刷 35岁找不到工作,绝对不会是软件开发人员的结局 用爱发电项目开发两个月的心得体会 以魏延“子午谷奇谋”讨论软件需求可行性问题 MQTT协议中可变长度的具体计算方式(有计算过程解析) 关于游戏服务器配置表功能的探讨 Java并发编程中上锁的几种方式 CSAPP第二章-信息的表示与处理 我的自我介绍 Windows XP虚拟机中文版无需激活下载 Java TreeSet的一些用法和特性 Linux C++ Socket实战 传统软件服务器与游戏服务器架构区别 独立个人项目开发心得 - 任务切分、挑战性、实用性和半途而废 使用Python实现简单UDP Ping 使用Python开发一个简单的web服务器 Kotlin手动实现一个最简单的哈希表 Kotlin实现二叉堆、大顶堆、优先级队列 搭建Spark实战环境(3台linux虚拟机集群)(一)样板机的搭建 Springboot操作MongoDB,包括增改查及复杂操作 Unison在Linux下的安装与使用 Java实现类似WINSCP访问远程Linux服务器,执行命令、上传文件、下载文件 一个被废弃的项目——自动爬取信息然后发给我自己邮箱上 Python连接MongoDB和Oracle实战 MongoDB常用查询语句 vue和springboot项目部署到Linux服务器 Python的一些用法(可能不定时更新) java正则表达式 - 双反斜杠(\)和Pattern的matches()与find() 简述爬虫对两种网站的不同爬取方式 Vue的路由配置及手动改地址栏为啥又跳转回来?? [JavaScript]JS基础知识 [Mybatis]逆向工程中Select语句查询不出‘TEXT’字段 [编译原理]FIRST、FOLLOW和SELECT [Spring]Spring学习笔记 [算法]分布估计算法 - 一种求解多维背包问题的混合分布估计算法_王凌 [日常]我做独立博客的原因 人间值得 深入理解计算机系统 想想就开心! 最重要的事,只有一件 藏书 被讨厌的勇气 关于 简历 朋友 尊重自己:给予与接收的心灵艺术
如何用C++分割一个字符串?
JonathanLin · 2023-07-02 · via 风萧古道 - 勤学苦练,年复一年

前言

在上机面试的时候,遇到了一道题,它的输入是两行字符串,每行字符串有未知数量的数字(两行数字数量一致),用空格分隔开,输入形如:

12 34 567 888 99 100
358 74 58454 742 4469 88

并不提前提供每行的数字数量。而是让用户自己切分。

当时在上机考试时,我没有使用C++实现这一功能,而是使用Java里的split()进行处理。

后来,考试结束后,我上网查询C++切分字符串的写法,发现C++并没有原生提供类似split(某个字符)的写法。

那么有什么方法能替代呢?

方法1:使用string的find等函数()配合substr()进行切分

根据知乎大佬的回答,他提供的第一种解决方案是:

C++ 的 string 为什么不提供 split 函数? - 知乎用户的回答 - 知乎 https://www.zhihu.com/question/36642771/answer/865135551

#include <iostream>
#include <cstring>
#include <vector>
void split(const std::string& s, std::vector<std::string>& tokens, const std::string& delimiters = " ") {
    std::string::size_type lastPos = s.find_first_not_of(delimiters, 0);
    std::string::size_type pos = s.find_first_of(delimiters, lastPos);
    while (std::string::npos != pos || std::string::npos != lastPos) {
        tokens.push_back(s.substr(lastPos, pos - lastPos));
        lastPos = s.find_first_not_of(delimiters, pos);
        pos = s.find_first_of(delimiters, lastPos);
    }
}
int main() {
    std::string str = "12 34 567 888 99 100";
    std::vector<std::string> res;
    split(str, res, " ");
    for (auto r: res) {
        printf("%s\n", r.c_str());
    }
    return 0;
}

输出结果:

12
34
567
888
99
100

这个split()函数,通过记录两个下标来确定需要裁出的字符串。

  • 第一个下标lastPos,寻找从字符串开始后,第一个不是分隔符的字符下标;
  • 第二个下标pos,寻找从lastPos之后,第一个是分隔符的字符下标。 如此一来,从lastPos到pos之间的字符串,就是我们需要裁出的字符串。

裁出第一个字符串后,将两个下标按之前的逻辑往后移动,直到两个下标都找不到合适的值(返回string::npos)时,结束。

函数介绍:

find_first_of()

string的find_first_of()接收两个参数,第一个参数是要寻找的字符,它可能是string,char*或者char,第二个参数是开始寻找的下标(可以不传,默认传0)。 从第二个参数所指的字符串数组下标开始,往后寻找,直到找到要寻找的字符时,返回找到字符的下标。

find_first_not_of()

string的find_first_not_of()接收的参数与find_first_of()一致,但它是寻找直到不是寻找字符时,返回不是寻找字符的下标。

substr()

用于切分字符串,接收两个参数pos和len,从pos位置开始,切出往后len个长度的字符。不会修改原字符串。

具体的C++文档位置:

find_first_of():https://cplusplus.com/reference/string/string/find_first_of/ find_first_not_of():https://cplusplus.com/reference/string/string/find_first_not_of/ substr():https://cplusplus.com/reference/string/string/substr/

方法2:C++11 正则表达式

#include <iostream>
#include <vector>
#include <regex>
int main() {
    std::string str = "12 34 567 888 99 100";
  
    std::regex ws_re("\\s+");
    std::vector<std::string> res(
        std::sregex_token_iterator(
            str.begin(), str.end(), ws_re, -1
        ),
        std::sregex_token_iterator()
    );
    for (auto r: res) {
        printf("%s\n", r.c_str());
    }
    return 0;
}

这个例子是来自regex_token_iterator的介绍中。

https://en.cppreference.com/w/cpp/regex/regex_token_iterator

使用sregex_token_iterator()迭代器来进行切分操作(前面的s指代使用字符串类型string)。在这个例子中,9-11行中,构造了一个sregex_token_iterator迭代器,传入了4个参数,分别是:字符串的开始位置的迭代器,字符串结束的迭代器,正则表达式对象,是否使用匹配的部分(0使用,-1不使用)。

构造sregex_token_iterator()时,确定了要从字符串开始位置,查找到字符串末尾,通过正则表达式匹配,然后查找不匹配的部分。

该迭代器的末尾,是一个默认构造的sregex_token_iterator()对象。

vector的构造函数中,可以通过传入两个迭代器,获取迭代器之间的元素。

方法3:使用stringstream分割字符串(仅支持空格、回车、tab换行)

信息来源:https://www.cnblogs.com/narjaja/p/10044157.html

#include <iostream>
#include <sstream>
#include <vector>
int main() {
    std::string str = "12 34 567 888 99 100";
    std::vector<std::string> res;
    std::istringstream ss(str);
    std::string word;
    while(ss>>word) {
        res.push_back(word);
    }
 
    for (auto r: res) {
        printf("%s\n", r.c_str());
    }
    return 0;
}

通过C++的 » ,像用户cin一样,将字符串“输入”,从而得到切分的效果。

如果要支持自定义分隔符,则可以使用getline()进行处理

#include <iostream>
#include <sstream>
#include <vector>
 
using namespace std;
 
int main() {
    std::string data = "1_2_3_4_5_6";
    std::stringstream ss(data);
    std::string item;
    cout << data << endl;
    while (std::getline(ss, item, '_')) 
        cout << item << ' ';  
}

方法4:通过C语言的strtok()函数实现

第一种错误写法

还有一种做法是通过C语言的strtok()函数进行处理。 在网上能找到类似这样结构的代码:

#include <iostream>
#include <cstring>
int main() {
    char str[] = "A B C D";
    char* p = strtok(str, " ");
    std::cout << p << std::endl;
    while (p != NULL) {
        p = strtok(NULL, " ");
        std::cout << p << std::endl;
    }

    return 0;
}

也能正常得到结果:

A
B
C
D

但是,当我打算在第11行进行cout输出时,发现结果并不会正常输出:

#include <iostream>
#include <cstring>
int main() {
    char str[] = "A B C D";
    char* p = strtok(str, " ");
    std::cout << p << std::endl;
    while (p != NULL) {
        p = strtok(NULL, " ");
        std::cout << p << std::endl;
    }
    std::cout << "abc" << std::endl;
    return 0;
}

此时输出的结果还是:

A
B
C
D

并没有最后的abc。

问了ChatGPT后,得到的解释是:

这段C++代码中的问题是在循环输出切割后的子字符串时,先打印p的值再进行下一次循环。这会导致在最后一次循环结束后,p被设置为NULL,然后再尝试打印p的值,导致未定义行为。

在第8行,你打印了p的值,然后在第9行将其设置为下一个切割后的子字符串。接下来,在第10行的循环开始时,你再次打印p的值,但此时它可能已经为NULL,导致未定义行为。

为了修复这个问题,你可以将打印语句移到循环的末尾,这样先进行下一次循环再打印p的值,确保p不为NULL。

以下是修正后的代码:

#include <iostream>
#include <cstring>

int main() {
    char str[] = "A B C D";
    char* p = strtok(str, " ");
    while (p != NULL) {
        std::cout << p << std::endl;
        p = strtok(NULL, " ");
    }
    std::cout << "abc" << std::endl;
    return 0;
}

这样修改后,代码将正常输出切割后的子字符串,然后输出"abc"。请注意,std::endl用于在输出后添加换行符,以保证输出结果的正确格式。

第二种错误写法

#include <stdio.h>
#include<string.h>
int main()
{
  char s[] = "A B C D";
  char *p; 
  const char *d = " ";
  p = strtok(s, d);

  while(p)
  {  
    printf("%s\n", p);  
    p = strtok(NULL, " ");  

  }
  printf("abc");
  return 0;
}

这一次我们在循环体中,先输出上一次切分的p,再读取下一次的p。但字符串“abc”还是没有正常输出。ChatGPT解答如下:

ChatGPT

这段代码中无法正常输出"abc"的原因是因为在使用strtok函数对字符串进行分割时,它会将原字符串中的分隔符替换为’\0’(空字符)来分割子字符串。这导致原字符串s被修改,后续的printf语句在遇到’\0’时会提前结束。

在代码中,原字符串s的内容为"A B C D",而strtok(s, d)会将它分割成"A"、“B”、“C"和"D"四个子字符串。因此,循环中的printf语句会输出这四个子字符串,分别为"A”、“B”、“C"和"D”。

然后,在最后一次调用strtok(NULL, " “)时,由于已经没有分隔符了,strtok会返回NULL,结束循环。然后,代码中的printf(“abc”)会尝试输出"abc”,但是由于之前的strtok调用修改了原字符串s,将其中的空格替换为了’\0’,所以在输出"abc"之前,printf函数遇到了’\0’字符,将其视为字符串的结尾,导致输出提前结束。

如果你希望能够输出"abc",你可以在printf语句中添加换行符\n,如下所示:

#include <stdio.h>
#include <string.h>

int main()
{
  char s[] = "A B C D";
  char *p;
  const char *d = " ";
  p = strtok(s, d);

  while (p)
  {
    printf("%s\n", p);
    p = strtok(NULL, " ");
  }

  printf("abc\n");  // 在"abc"后添加换行符\n
  return 0;
}

通过上述修改,代码将能够正常输出"A"、“B”、“C”、“D"和"abc”。

strtok()函数

strtok()函数切割后,会在切断的位置上填充\0,会修改原字符串。所以一定要多加注意,慎用这个函数。