我想用 C++ 编写一些标记字符串的东西。为了解释我想要什么,请使用以下字符串:
add string "this is a string with spaces!"
这必须拆分如下:
add
string
this is a string with spaces!
是否有快速且基于标准库的方法?
不需要图书馆。迭代可以完成任务(如果它像您描述的那样简单)。
string str = "add string \"this is a string with space!\"";
for( size_t i=0; i<str.length(); i++){
char c = str[i];
if( c == ' ' ){
cout << endl;
}else if(c == '\"' ){
i++;
while( str[i] != '\"' ){ cout << str[i]; i++; }
}else{
cout << c;
}
}
输出
add
string
this is a string with space!
我想知道为什么这里没有介绍这个简单的 C++ 风格的解决方案。这是基于这样一个事实,如果我们首先用 分割字符串\"
,那么每个偶数块都是“内部”引号,并且每个奇数块应该另外被空格分割。
out_of_range 或其他任何事情都没有可能。
unsigned counter = 0;
std::string segment;
std::stringstream stream_input(input);
while(std::getline(stream_input, segment, '\"'))
{
++counter;
if (counter % 2 == 0)
{
if (!segment.empty())
std::cout << segment << std::endl;
}
else
{
std::stringstream stream_segment(segment);
while(std::getline(stream_segment, segment, ' '))
if (!segment.empty())
std::cout << segment << std::endl;
}
}
这是它的完整功能。根据需要对其进行修改,它将部分字符串添加到向量strings( qargs
)中。
void split_in_args(std::vector<std::string>& qargs, std::string command){
int len = command.length();
bool qot = false, sqot = false;
int arglen;
for(int i = 0; i < len; i++) {
int start = i;
if(command[i] == '\"') {
qot = true;
}
else if(command[i] == '\'') sqot = true;
if(qot) {
i++;
start++;
while(i<len && command[i] != '\"')
i++;
if(i<len)
qot = false;
arglen = i-start;
i++;
}
else if(sqot) {
i++;
start++;
while(i<len && command[i] != '\'')
i++;
if(i<len)
sqot = false;
arglen = i-start;
i++;
}
else{
while(i<len && command[i]!=' ')
i++;
arglen = i-start;
}
qargs.push_back(command.substr(start, arglen));
}
for(int i=0;i<qargs.size();i++){
std::cout<<qargs[i]<<std::endl;
}
std::cout<<qargs.size();
if(qot || sqot) std::cout<<"One of the quotes is open\n";
}
Boost 库有一个tokenizer
可以接受escaped_list_separator
. 这些组合看起来可能会提供您正在寻找的东西。
这里是 boost 文档的链接,在这篇文章中是最新的,在你阅读这篇文章时几乎可以肯定是旧版本。
https://www.boost.org/doc/libs/1_73_0/libs/tokenizer/doc/tokenizer.htm
https://www.boost.org/doc/libs/1_73_0/libs/tokenizer/doc/escaped_list_separator.htm
这个例子是从 boost 文档中偷来的。请原谅我没有创建自己的示例。
// simple_example_2.cpp
#include<iostream>
#include<boost/tokenizer.hpp>
#include<string>
int main(){
using namespace std;
using namespace boost;
string s = "Field 1,\"putting quotes around fields, allows commas\",Field 3";
tokenizer<escaped_list_separator<char> > tok(s);
for(tokenizer<escaped_list_separator<char> >::iterator beg=tok.begin(); beg!=tok.end();++beg){
cout << *beg << "\n";
}
}
我会定义一个类Token
来从流中读取单个令牌。
然后使用您的代码变得非常简单。
#include <iostream>
#include <string>
int main()
{
// Simply read the tokens from the stream.
Token t;
while(std::cin >> t)
{
std::cout << "Got: " << t << "\n";
}
}
像这样的流对象非常容易编写:
class Token
{
// Just something to store the value in.
std::string value;
// Then define the input and output operators.
friend std::ostream& operator<<(std::ostream& str, Token const& output)
{
return str << output.value;
}
// Input is slightly harder than output.
// but not that difficult to get correct.
friend std::istream& operator>>(std::istream& str, Token& input)
{
std::string tmp;
if (str >> tmp)
{
if (tmp[0] != '"')
{
// We read a word that did not start with
// a quote mark. So we are done. Simply put
// it in the destination.
input.value = std::move(tmp);
}
else if (tmp.front() == '"' && tmp.back() == '"')
{
// we read a word with both open and close
// braces so just nock these off.
input.value = tmp.substr(1, tmp.size() - 2);
}
else
{
// We read a word that has but has a quote at the
// start. So need to get all the characters upt
// closing quote then add this to value.
std::string tail;
if (std::getline(str, tail, '"'))
{
// Everything worked
// update the input
input.value = tmp.substr(1) + tail;
}
}
}
return str;
}
};
我想标准库没有直接的方法。间接遵循算法将起作用:
a) 使用 搜索 '\"' string::find('\"')
。如果找到任何内容,使用 搜索下一个 '\"' string::find('\'',prevIndex)
,如果找到则使用string::substr()
。从原始字符串中丢弃该部分。
b) 现在' '
以同样的方式搜索字符。
注意:您必须遍历整个字符串。
这是我的解决方案,相当于python的shlex,shlex_join()是shlex_split()的逆:
#include <cctype>
#include <iomanip>
#include <iostream>
#include <string>
#include <sstream>
#include <utility>
#include <vector>
// Splits the given string using POSIX shell-like syntax.
std::vector<std::string> shlex_split(const std::string& s)
{
std::vector<std::string> result;
std::string token;
char quote{};
bool escape{false};
for (char c : s)
{
if (escape)
{
escape = false;
if (quote && c != '\\' && c != quote)
token += '\\';
token += c;
}
else if (c == '\\')
{
escape = true;
}
else if (!quote && (c == '\'' || c == '\"'))
{
quote = c;
}
else if (quote && c == quote)
{
quote = '\0';
if (token.empty())
result.emplace_back();
}
else if (!isspace(c) || quote)
{
token += c;
}
else if (!token.empty())
{
result.push_back(std::move(token));
token.clear();
}
}
if (!token.empty())
{
result.push_back(std::move(token));
token.clear();
}
return result;
}
// Concatenates the given token list into a string. This function is the
// inverse of shlex_split().
std::string shlex_join(const std::vector<std::string>& tokens)
{
auto it = tokens.begin();
if (it == tokens.end())
return {};
std::ostringstream oss;
while (true)
{
if (it->empty() || it->find_first_of(R"( "\)") != std::string::npos)
oss << std::quoted(*it);
else
oss << *it;
if (++it != tokens.end())
oss << ' ';
else
break;
}
return oss.str();
}
void test(const std::string& s, const char* expected = nullptr)
{
if (!expected)
expected = s.c_str();
if (auto r = shlex_join(shlex_split(s)); r != expected)
std::cerr << '[' << s << "] -> [" << r << "], expected [" << expected << "]\n";
}
int main()
{
test("");
test(" ", "");
test("a");
test(" a ", "a");
test("a b", "a b");
test(R"(a \s b)", "a s b");
test(R"("a a" b)");
test(R"('a a' b)", R"("a a" b)");
test(R"(a \" b)", R"(a "\"" b)");
test(R"(a \\ b)", R"(a "\\" b)");
test(R"("a \" a" b)");
test(R"('a \' a' b)", R"("a ' a" b)");
test(R"("a \\ a" b)");
test(R"('a \\ a' b)", R"("a \\ a" b)");
test(R"('a \s a' b)", R"("a \\s a" b)");
test(R"("a \s a" b)", R"("a \\s a" b)");
test(R"('a \" a' b)", R"("a \\\" a" b)");
test(R"("a \' a" b)", R"("a \\' a" b)");
test(R"("" a)");
test(R"('' a)", R"("" a)");
test(R"(a "")");
test(R"(a '')", R"(a "")");
}
在 C++14 或更高版本中有一种基于标准库的方法。但这并不快。
#include <iomanip> // quoted
#include <iostream>
#include <sstream> // stringstream
#include <string>
using namespace std;
int main(int argc, char **argv) {
string str = "add string \"this is a string with spaces!\"";
stringstream ss(str);
string word;
while (ss >> quoted(word)) {
cout << word << endl;
}
return 0;
}