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

推荐订阅源

Jina AI
Jina AI
Google DeepMind News
Google DeepMind News
C
Cybersecurity and Infrastructure Security Agency CISA
T
Tenable Blog
T
The Exploit Database - CXSecurity.com
Latest news
Latest news
G
GRAHAM CLULEY
Project Zero
Project Zero
L
Lohrmann on Cybersecurity
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Cyber Attacks, Cyber Crime and Cyber Security
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Webroot Blog
Webroot Blog
Help Net Security
Help Net Security
TaoSecurity Blog
TaoSecurity Blog
Hacker News: Ask HN
Hacker News: Ask HN
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
N
News and Events Feed by Topic
Cisco Talos Blog
Cisco Talos Blog
T
Tor Project blog
The Hacker News
The Hacker News
The Last Watchdog
The Last Watchdog
C
CXSECURITY Database RSS Feed - CXSecurity.com
V2EX - 技术
V2EX - 技术
S
Secure Thoughts
AWS News Blog
AWS News Blog
W
WeLiveSecurity
云风的 BLOG
云风的 BLOG
V
V2EX
Last Week in AI
Last Week in AI
雷峰网
雷峰网
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
G
Google Developers Blog
P
Palo Alto Networks Blog
A
Arctic Wolf
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
M
MIT News - Artificial intelligence
V
Visual Studio Blog
C
CERT Recently Published Vulnerability Notes
WordPress大学
WordPress大学
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Threatpost
Simon Willison's Weblog
Simon Willison's Weblog
PCI Perspectives
PCI Perspectives
量子位
K
Kaspersky official blog
腾讯CDC
Schneier on Security
Schneier on Security
F
Full Disclosure
S
Schneier on Security

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

游戏服务器开发经验(五)应对复杂需求 沉浸式体验东汉末年生活 - 《真三国无双 起源》玩后感 怒其不争!致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,会修改原字符串。所以一定要多加注意,慎用这个函数。