用字符拆分字符串

时间:2012-04-07 21:39:01

标签: c++ string algorithm

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

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

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

注意:我想强调这一点,因为人们可能会问“蛮力怎么行不通”。我的强力解决方案是创建一个循环,并使用里面的 substr()函数。但是,由于它需要起点和长度,因此当我想分割日期时它会失败。因为用户可能会在7/12/2012或07/3/2011输入它,在计算“/”分隔符的下一个位置之前,我可以确切地说出长度。

10 个答案:

答案 0 :(得分:75)

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

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" };

答案 1 :(得分:12)

喜欢RegEx的人的另一种方式(C ++ 11 / boost)。就我个人而言,我是这种数据的忠实粉丝。 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));
}

答案 2 :(得分:10)

Boost在algorithm/string.hpp中寻找 split()

std::string sample = "07/3/2011";
std::vector<string> strs;
boost::split(strs, sample, boost::is_any_of("/"));

答案 3 :(得分:4)

另一种可能性是使用使用特殊ctype方面的区域设置来添加流。流使用ctype facet来确定它被视为分隔符的&#34;&#34; whitespace&#34;。使用ctype facet将分隔符分类为空格,读取可能非常简单。这是实现方面的一种方法:

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_iterator来初始化一个向量来读取字符串中的片段(istringstream中嵌入的片段):

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

如果你只在一个地方使用它,这显然会导致过度杀伤。但是,如果你使用它很多,它可以很长时间地保持代码的其余部分非常干净。

答案 4 :(得分:2)

查看boost::tokenizer

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

答案 5 :(得分:2)

我天生不喜欢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 == 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!

答案 6 :(得分:2)

由于尚未有人发布此内容: 解决方案使用 ranges 非常简单。您可以使用 std::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 的简单 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

答案 7 :(得分:0)

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

string class
string .find()
string .c_str()

答案 8 :(得分:0)

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"

答案 9 :(得分: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);
}