string的split
C++ 的 string 为什么不提供 split 函数?
参考文档: https://www.zhihu.com/question/36642771
C++11以前有很多原因不能提供一个通用的split,比如说需要考虑split以后的结果存储在什么类型的容器中,可以是vector、list等等包括自定义容器,很难提供一个通用的;再比如说需要split的源字符串很大的时候运算的时间可能会很长,所以这个split最好是lazy的,每次只返回一条结果。
C++11之前只能自己写,我目前发现的史上最优雅的一个实现是这样的:
void split(const string &s, vector<string> &tokens, const string &delimiters = " ")
{
string::size_type lastPos = s.find_first_not_of(delimiters, 0);
string::size_type pos = s.find_first_of(delimiters, lastPos);
while (string::npos != pos || string::npos != lastPos)
{
tokens.push_back(s.substr(lastPos, pos - lastPos));
//use emplace_back after C++11
lastPos = s.find_first_not_of(delimiters, pos);
pos = s.find_first_of(delimiters, lastPos);
}
}
从C++11开始,标准库中提供了regex,regex用来做split就是小儿科了,比如:
std::string text = "Quick brown fox.";
std::regex ws_re("\\s+"); // whitespace
std::vector<std::string> v(std::sregex_token_iterator(text.begin(), text.end(), ws_re, -1), std::sregex_token_iterator());
for (auto &&s : v)
std::cout << s << "\n";
C++17提供的string_view可以加速上面提到的第一个split实现,减少拷贝,性能有不小提升,参看此文:Speeding Up string_view String Split Implementation。
从C++20开始,标准库中提供了ranges,有专门的split view,只要写str | split(’ ‘)就可以切分字符串,如果要将结果搜集到vector中,可以这样用(随手写的,可能不是最简):
string str("hello world test split");
auto sv = str
| ranges::views::split(' ')
| ranges::views::transform([](auto &&i){
return i | ranges::to<string>(); })
|ranges::to<vector>();
for (auto &&s : sv)
{
cout << s << "\n";
}
其实C语言里面也有一个函数strtok用于char*的split,例如:
#include <iostream>
#include <string>
using namespace std;
int main() {
string str = "one two three four five";
char *token = strtok(str.data(), " "); // non-const data() needs c++17
while (token != NULL)
{
std::cout << token << '\n';
token = strtok(NULL, " ");
}
}
这里要注意的是strtok的第一个参数类型是char*
而不是const char*
,实际上strtok的确会改变输入的字符串。
换行符拆分
参考文档: https://www.techiedelight.com/zh/split-a-string-on-newlines-in-cpp/
使用 std::getline
在换行符上拆分字符串的一个简单解决方案是使用 std::getline 功能。它可用于从由换行符分隔的输入流中提取标记,如下所示:
#include <iostream>
#include <string>
#include <vector>
#include <regex>
std::vector<std::string> splitString(const std::string& str)
{
std::vector<std::string> tokens;
std::stringstream ss(str);
std::string token;
while (std::getline(ss, token, '\n')) {
tokens.push_back(token);
}
return tokens;
}
int main()
{
std::string str = "C\nC++\nJava";
std::vector<std::string> tokens = splitString(str);
for (auto const &token: tokens) {
std::cout << token << std::endl;
}
return 0;
}
使用 string::find
这 std::string::find
成员函数从指定位置开始在字符串中搜索指定字符。它返回指定字符的第一次出现和 string::npos
如果没有找到。它可以按如下方式在换行符上拆分字符串:
#include <iostream>
#include <string>
#include <vector>
std::vector<std::string> splitString(const std::string& str)
{
std::vector<std::string> tokens;
std::string::size_type pos = 0;
std::string::size_type prev = 0;
while ((pos = str.find('\n', prev)) != std::string::npos) {
tokens.push_back(str.substr(prev, pos - prev));
prev = pos + 1;
}
tokens.push_back(str.substr(prev));
return tokens;
}
int main()
{
std::string str = "C\nC++\nJava";
std::vector<std::string> tokens = splitString(str);
for (auto const &token: tokens) {
std::cout << token << std::endl;
}
return 0;
}
使用升压
最后,我们可以使用 boost::algorithm::split_regex
boost 库提供的算法,用于将输入序列拆分为由分隔符分隔的标记。此函数等效于 C strtok,在头文件中可用 <boost/algorithm/string/regex.hpp>
.
#include <iostream>
#include <string>
#include <vector>
#include <regex>
#include <boost/regex.hpp>
#include <boost/algorithm/string/regex.hpp>
std::vector<std::string> splitString(const std::string& str)
{
std::vector<std::string> tokens;
split_regex(tokens, str, boost::regex("(\r\n)+"));
return tokens;
}
int main()
{
std::string str = "C\nC++\nJava";
std::vector<std::string> tokens = splitString(str);
for (auto const &token: tokens) {
std::cout << token << std::endl;
}
return 0;
}
使用 std::sregex_token_iterator
#include <iostream>
#include <string>
#include <vector>
#include <regex>
int main()
{
std::string str = "adas\nsensor\ngnss\ninspvax\nInspvax.ap";
std::regex line_re("\n");
std::vector<std::string> lines(
std::sregex_token_iterator(str.begin(), str.end(), line_re, -1),
std::sregex_token_iterator());
for (auto s : lines)
{
std::cout << "line:---" << s << std::endl;
}
return 0;
}
文件中带\n
的字符串
文件内容: cat data
abc\n123\ngnss\ninspvax\nInspvax
上面代码:
#include <iostream>
#include <string>
#include <vector>
#include <regex>
#include <fstream>
int main()
{
std::ifstream infile;
infile.open("./data");
std::ostringstream tmp;
tmp << infile.rdbuf();
std::string str = tmp.str();
infile.close();
std::regex line_re("\n");
std::vector<std::string> lines(
std::sregex_token_iterator(str.begin(), str.end(), line_re, -1),
std::sregex_token_iterator());
for (auto s : lines)
{
std::cout << "line:---" << s << std::endl;
}
return 0;
}
输出:
line:---abc\n123\ngnss\ninspvax\nInspvax
正确分割方式
#include <iostream>
#include <string>
#include <vector>
#include <regex>
#include <fstream>
int main()
{
std::ifstream infile;
infile.open("./data");
std::ostringstream tmp;
tmp << infile.rdbuf();
std::string str = tmp.str();
infile.close();
std::regex line_re("\\\\n");
std::vector<std::string> lines(
std::sregex_token_iterator(str.begin(), str.end(), line_re, -1),
std::sregex_token_iterator());
for (auto s : lines)
{
std::cout << "line:---" << s << std::endl;
}
return 0;
}
输出:
line:---abc
line:---123
line:---gnss
line:---inspvax
line:---Inspvax
原因: 文件中换行\n
并不是以字符方式展现,如果文件中字符串存在\n
它就是一个字符串。