63

我知道这是一个非常简单的问题,但我只想一劳永逸地为自己解决它

我只想使用字符作为拆分分隔符将字符串拆分为数组。(很像 C# 著名的.Split()函数。我当然可以应用蛮力方法,但我想知道是否有比这更好的方法。

到目前为止,我已经搜索过并且可能最接近的解决方案方法是使用strtok(),但是由于它的不便(将您的字符串转换为 char 数组等),我不喜欢使用它。有没有更简单的方法来实现这个?

注意:我想强调这一点,因为人们可能会问“为什么蛮力不起作用”。我的蛮力解决方案是创建一个循环,并在里面使用substr()函数。但是,由于它需要起点和长度,所以当我想拆分日期时它会失败。因为用户可能将其输入为 2012 年 7 月 12 日或 2011 年 7 月 3 日,在计算“/”分隔符的下一个位置之前,我可以真正知道长度。

4

12 回答 12

125

使用向量、字符串和字符串流。有点麻烦,但可以解决问题。

#include <string>
#include <vector>
#include <sstream>

std::stringstream test("this_is_a_test_string");
std::string segment;
std::vector<std::string> seglist;

while(std::getline(test, segment, '_'))
{
   seglist.push_back(segment);
}

这会产生一个与以下内容相同的向量

std::vector<std::string> seglist{ "this", "is", "a", "test", "string" };
于 2012-04-07T21:59:19.270 回答
24

Boost 具有您正在寻找的split()algorithm/string.hpp

std::string sample = "07/3/2011";
std::vector<std::string> strs;
boost::split(strs, sample, boost::is_any_of("/"));
于 2012-04-07T22:04:05.027 回答
16

喜欢 RegEx 的人的另一种方式(C++11/boost)。就我个人而言,我是此类数据的 RegEx 的忠实粉丝。IMO 它比简单地使用分隔符拆分字符串要强大得多,因为如果您愿意,您可以选择更聪明地了解“有效”数据的构成。

#include <string>
#include <algorithm>    // copy
#include <iterator>     // back_inserter
#include <regex>        // regex, sregex_token_iterator
#include <vector>

int main()
{
    std::string str = "08/04/2012";
    std::vector<std::string> tokens;
    std::regex re("\\d+");

    //start/end points of tokens in str
    std::sregex_token_iterator
        begin(str.begin(), str.end(), re),
        end;

    std::copy(begin, end, std::back_inserter(tokens));
}
于 2012-04-07T22:04:11.797 回答
4

另一种可能性是为流注入使用特殊ctype方面的语言环境。流使用 ctype 方面来确定什么是“空白”,它将其视为分隔符。使用将分隔符分类为空格的 ctype 方面,读取可能非常简单。这是实现方面的一种方法:

struct field_reader: std::ctype<char> {

    field_reader(): std::ctype<char>(get_table()) {}

    static std::ctype_base::mask const* get_table() {
        static std::vector<std::ctype_base::mask> 
            rc(table_size, std::ctype_base::mask());

        // we'll assume dates are either a/b/c or a-b-c:
        rc['/'] = std::ctype_base::space;
        rc['-'] = std::ctype_base::space;
        return &rc[0];
    }
};

我们使用它imbue来告诉流使用包含它的语言环境,然后从该流中读取数据:

std::istringstream in("07/3/2011");
in.imbue(std::locale(std::locale(), new field_reader);

有了它,拆分变得几乎是微不足道的——只需使用几个istream_iterators 初始化一个向量来从字符串中读取片段(嵌入在 中istringstream):

std::vector<std::string>((std::istream_iterator<std::string>(in),
                          std::istream_iterator<std::string>());

显然,如果您只在一个地方使用它,这往往会过度杀伤力。但是,如果您经常使用它,它可以在很大程度上保持其余代码的干净。

于 2012-04-08T03:49:48.557 回答
4

我天生不喜欢stringstream,虽然我不知道为什么。今天,我编写了这个函数来允许将std::string任意字符或字符串拆分为向量。我知道这个问题很老,但我想分享另一种拆分方式std::string

此代码完全省略了您从结果中拆分的字符串部分,尽管可以轻松修改它以包含它们。

#include <string>
#include <vector>

void split(std::string str, std::string splitBy, std::vector<std::string>& tokens)
{
    /* Store the original string in the array, so we can loop the rest
     * of the algorithm. */
    tokens.push_back(str);

    // Store the split index in a 'size_t' (unsigned integer) type.
    size_t splitAt;
    // Store the size of what we're splicing out.
    size_t splitLen = splitBy.size();
    // Create a string for temporarily storing the fragment we're processing.
    std::string frag;
    // Loop infinitely - break is internal.
    while(true)
    {
        /* Store the last string in the vector, which is the only logical
         * candidate for processing. */
        frag = tokens.back();
        /* The index where the split is. */
        splitAt = frag.find(splitBy);
        // If we didn't find a new split point...
        if(splitAt == std::string::npos)
        {
            // Break the loop and (implicitly) return.
            break;
        }
        /* Put everything from the left side of the split where the string
         * being processed used to be. */
        tokens.back() = frag.substr(0, splitAt);
        /* Push everything from the right side of the split to the next empty
         * index in the vector. */
        tokens.push_back(frag.substr(splitAt+splitLen, frag.size()-(splitAt+splitLen)));
    }
}

要使用,只需像这样调用...

std::string foo = "This is some string I want to split by spaces.";
std::vector<std::string> results;
split(foo, " ", results);

您现在可以随意访问向量中的所有结果。就这么简单——不stringstream,没有第三方库,没有回退到 C!

于 2016-04-12T03:24:48.173 回答
4

由于尚未有人发布此内容:解决方案非常简单,使用ranges. 您可以使用 astd::ranges::views::split来分解输入,然后将输入转换为std::stringstd::string_view元素。

#include <ranges>


...

// The input to transform
const auto str = std::string{"Hello World"};

// Function to transform a range into a std::string
// Replace this with 'std::string_view' to make it a view instead.
auto to_string = [](auto&& r) -> std::string {
    const auto data = &*r.begin();
    const auto size = static_cast<std::size_t>(std::ranges::distance(r));

    return std::string{data, size};
};

const auto range = str | 
                   std::ranges::views::split(' ') | 
                   std::ranges::views::transform(to_string);

for (auto&& token : str | range) {
    // each 'token' is the split string
}

这种方法实际上可以组合成任何东西,甚至是一个split返回 a 的简单函数std::vector<std::string>

auto split(const std::string& str, char delimiter) -> std::vector<std::string>
{
    const auto range = str | 
                       std::ranges::views::split(delimiter) | 
                       std::ranges::views::transform(to_string);

    return {std::ranges::begin(range), std::ranges::end(range)};
}

Live Example

于 2021-04-13T20:24:20.930 回答
2

看看boost::tokenizer

如果您想汇总自己的方法,可以使用std::string::find()确定拆分点。

于 2012-04-07T21:40:35.597 回答
0

您是否有不想将 a 转换string为字符数组 ( char*) 的原因?调用起来相当容易.c_str()。您还可以使用循环和.find()函数。

字符串类
字符串 .find()
字符串 .c_str()

于 2012-04-07T22:02:33.537 回答
0

对于那些没有(想要,需要)C++20这个C++11解决方案的人来说可能是一种选择。

它在输出迭代器上进行了模板化,因此您可以提供自己的目的地,拆分项目应附加到该目的地,并提供如何处理多个连续分隔字符的选择。

是的,它使用std::regex但很好,如果你已经在 C++11 快乐的土地上,为什么不使用它。

////////////////////////////////////////////////////////////////////////////
//
// Split string "s" into substrings delimited by the character "sep"
// skip_empty indicates what to do with multiple consecutive separation
// characters:
//
// Given s="aap,,noot,,,mies"
//       sep=','
//
// then output gets the following written into it:
//      skip_empty=true  => "aap" "noot" "mies"
//      skip_empty=false => "aap" "" "noot" "" "" "mies"
//
////////////////////////////////////////////////////////////////////////////
template <typename OutputIterator>
void string_split(std::string const& s, char sep, OutputIterator output, bool skip_empty=true) {
    std::regex  rxSplit( std::string("\\")+sep+(skip_empty ? "+" : "") );

    std::copy(std::sregex_token_iterator(std::begin(s), std::end(s), rxSplit, -1),
              std::sregex_token_iterator(), output);
}
于 2021-05-07T10:57:48.427 回答
0

我知道这个解决方案不合理,但它是有效的。此处提供此方法是为了作为当前问题解决方案的一种变体。

#include <iostream>
#include <vector>
#include <string>
using namespace std;
const int maximumSize=40;
vector<int> visited(maximumSize, 0);
string word;
void showContentVectorString(vector<string>& input)
{
    for(int i=0; i<input.size(); ++i)
    {
        cout<<input[i]<<", ";
    }
    return;
}
void dfs(int current, int previous, string& input, vector<string>& output, char symbol)
{
    if(visited[current]==1)
    {
        return;
    }
    visited[current]=1;
    string stringSymbol;
    stringSymbol.push_back(symbol);
    if(input[current]!=stringSymbol[0])
    {
        word.push_back(input[current]);
    }
    else
    {
        output.push_back(word);
        word.clear();
    }
    if(current==(input.size()-1))
    {
        output.push_back(word);
        word.clear();
    }
    for(int next=(current+1); next<input.size(); ++next)
    {
        if(next==previous)
        {
            continue;
        }
        dfs(next, current, input, output, symbol);
    }
    return;
}
void solve()
{
    string testString="this_is_a_test_string";
    vector<string> vectorOfStrings;
    dfs(0, -1, testString, vectorOfStrings, '_');
    cout<<"vectorOfStrings <- ";
    showContentVectorString(vectorOfStrings);
    return;
}
int main()
{
    solve();
    return 0;
}

结果如下:

vectorOfStrings <- this, is, a, test, string,
于 2021-12-09T14:08:30.257 回答
0

我已经使用了很长一段时间的一个解决方案是可以与向量和列表一起使用的拆分

#include <vector>
#include <string>
#include <list>

template< template<typename,typename> class Container, typename Separator >
Container<std::string,std::allocator<std::string> > split( const std::string& line, Separator sep ) {
    std::size_t pos = 0;
    std::size_t next = 0;
    Container<std::string,std::allocator<std::string> > fields;
    while ( next != std::string::npos ) {
        next = line.find_first_of( sep, pos );
        std::string field = next == std::string::npos ? line.substr(pos) : line.substr(pos,next-pos);
        fields.push_back(  field );
        pos = next + 1;
    }
    return fields;
}

int main() {
    auto res1 = split<std::vector>( "abc,def", ",:" );
    auto res2 = split<std::list>( "abc,def", ',' );
}
于 2021-12-19T05:02:35.353 回答
-2

erase()功能呢?如果您知道字符串中要拆分的确切位置,那么您可以使用erase().

std::string date("01/02/2019");
std::string day(date);
std::string month(date);
std::string year(date);

day.erase(2, string::npos); // "01"
month.erase(0, 3).erase(2); // "02"
year.erase(0,6); // "2019"
于 2019-08-19T13:30:15.520 回答