以惯用方法拆分string_view

时间:2017-12-28 18:28:34

标签: c++ c++17 string-view

我读了The most elegant way to iterate the words of a string并且很享受答案的简洁。现在我想对string_view做同样的事情。问题是,stringstream无法string_view

#include <iostream>
#include <string>
#include <sstream>
#include <algorithm>
#include <iterator>

int main() {
    using namespace std;
    string_view sentence = "And I feel fine...";
    istringstream iss(sentence); // <== error
    copy(istream_iterator<string_view>(iss),
         istream_iterator<string_view>(),
         ostream_iterator<string_view>(cout, "\n"));
}

有没有办法做到这一点?如果不是,那么这样的事情不是惯用的原因是什么?

4 个答案:

答案 0 :(得分:2)

如果您想使用该特定方法,则只需明确地将string_view转换为string

istringstream iss{string(sentence)}; // N.B. braces to avoid most vexing parse
copy(istream_iterator<string>(iss),
     istream_iterator<string>(),
     ostream_iterator<string_view>(cout, "\n"));

C ++标准库没有良好的字符串操作功能。您可能希望了解BoostAbseil等中的可用内容。其中任何一种都比这更好。

答案 1 :(得分:1)

stringstream拥有它所操作的字符串。这意味着它会创建给定字符串的副本。它不仅可以引用字符串。

即使使用建议的基于string_view的{​​{1}}类型,流仍然不是随机访问范围。他们没有办法处理字符串的子范围。这就是他们通过副本而不是通过迭代器或其他东西从流中提取数据的原因。

你想要的最好是通过一个基于stream的机制完成,因为它可以在不复制任何东西的情况下工作。它们可以与regex一起使用(尽管您必须手动构建string_view)。

答案 2 :(得分:0)

由定界符分割并返回vector<string_view>

设计用于快速拆分.csv文件中的行。

在{{1}必需的MSVC 2017 v15.9.6Intel Compiler v19.0编译下进行测试(C++17必需)。

string_view

请注意生命周期:#include <string_view> std::vector<std::string_view> Split(const std::string_view str, const char delim = ',') { std::vector<std::string_view> result; int indexCommaToLeftOfColumn = 0; int indexCommaToRightOfColumn = -1; for (int i=0;i<static_cast<int>(str.size());i++) { if (str[i] == delim) { indexCommaToLeftOfColumn = indexCommaToRightOfColumn; indexCommaToRightOfColumn = i; int index = indexCommaToLeftOfColumn + 1; int length = indexCommaToRightOfColumn - index; // Bounds checking can be omitted as logically, this code can never be invoked // Try it: put a breakpoint here and run the unit tests. /*if (index + length >= static_cast<int>(str.size())) { length--; } if (length < 0) { length = 0; }*/ std::string_view column(str.data() + index, length); result.push_back(column); } } const std::string_view finalColumn(str.data() + indexCommaToRightOfColumn + 1, str.size() - indexCommaToRightOfColumn - 1); result.push_back(finalColumn); return result; } 决不能超过父窗口string_view,因为它是一个窗口。如果父级string超出范围,则string指向的内容无效。在这种特殊情况下,API设计很难出错,因为输入/输出都是string_view,它们都是父字符串的所有窗口。最终在内存复制和CPU使用率方面非常有效。

请注意,如果使用string_view,唯一的缺点就是丢失了隐式null终止。因此,请使用支持string_view的函数,例如Boost中的string_view函数可将字符串转换为数字。

我用它来快速解析.csv文件。为了获得.csv文件中的每一行,我使用了lexical_castistringstream,它们的速度非常快(单个内核约2GB /秒或每秒1,200,000行)。

单元测试。使用Google Test进行测试(我使用vcpkg安装)。

getLine()

答案 3 :(得分:0)

Contango 的回答很好。为了适应我的项目中的 string 和 boost::string_view,我做了一些改动,并尝试摆脱复制构造函数。

以下代码将字符串拆分为string_view;

你必须保证字符串不会被破坏。

还有其他答案可能在语法上更优雅:请查看:https://www.bfilipek.com/2018/07/string-view-perf-followup.html。 上面有一个istringstream的版本,如果字符串本身很长,复制会有点问题,需要自己处理。


   typedef boost::string_view StringView; //Or you can just typedef std::string_view StringView;
#if defined(_WIN32) | defined(WIN32)
#pragma warning(push)
#pragma warning(disable:26486 26481)
#endif
        void SplitStringToStringView(const std::string& str, const char delim, std::vector<StringView>* outputPointer)
        {
            if (outputPointer == nullptr)
                return;

            std::vector<StringView>& result = *outputPointer;

            int indexCommaToLeftOfColumn = 0;
            int indexCommaToRightOfColumn = -1;

            const int end = boost::numeric_cast<int>(str.size());
            for (int i = 0; i < end; i++)
            {
                if (str.at(i) == delim)
                {
                    indexCommaToLeftOfColumn = indexCommaToRightOfColumn;
                    indexCommaToRightOfColumn = i;
                    const int index = indexCommaToLeftOfColumn + 1;
                    const int length = indexCommaToRightOfColumn - index;

                    // Bounds checking can be omitted as logically, this code can never be invoked 
                    // Try it: put a breakpoint here and run the unit tests.
                    /*if (index + length >= static_cast<int>(str.size()))
                    {
                        length--;
                    }
                    if (length < 0)
                    {
                        length = 0;
                    }*/

                    result.emplace_back(StringView(str.c_str() + index, length));
                }
            }
            const StringView finalColumn(str.c_str() + indexCommaToRightOfColumn + 1, 
                str.size() - indexCommaToRightOfColumn - 1);
            result.push_back(finalColumn);
        }
#if defined(_WIN32) | defined(WIN32)
#pragma warning(pop)
#endif

由于 Contango 提供了单元测试代码,非常好,我也应该这样做:

{
            const std::string str = "A,B,C";
            std::vector<StringView> tokens;
            SplitStringToStringView(str, ',', &tokens);
            EXPECT_TRUE(tokens.size() == 3);
            EXPECT_TRUE(tokens[0] == "A");
            EXPECT_TRUE(tokens[1] == "B");
            EXPECT_TRUE(tokens[2] == "C");
        }
        {
            const std::string str = ",B,C";
            std::vector<StringView> tokens;
            SplitStringToStringView(str, ',', &tokens);
            EXPECT_TRUE(tokens.size() == 3);
            EXPECT_TRUE(tokens[0] == "");
            EXPECT_TRUE(tokens[1] == "B");
            EXPECT_TRUE(tokens[2] == "C");
        }
        {
            const std::string str = "A,B,";
            std::vector<StringView> tokens;
            SplitStringToStringView(str, ',', &tokens);
            EXPECT_TRUE(tokens.size() == 3);
            EXPECT_TRUE(tokens[0] == "A");
            EXPECT_TRUE(tokens[1] == "B");
            EXPECT_TRUE(tokens[2] == "");
        }
        {
            const std::string str = "";
            std::vector<StringView> tokens;
            SplitStringToStringView(str, ',', &tokens);
            EXPECT_TRUE(tokens.size() == 1);
            EXPECT_TRUE(tokens[0] == "");
        }
        {
            const std::string str = "A";
            std::vector<StringView> tokens;
            SplitStringToStringView(str, ',', &tokens);
            EXPECT_TRUE(tokens.size() == 1);
            EXPECT_TRUE(tokens[0] == "A");
        }
        {
            const std::string str = ",";
            std::vector<StringView> tokens;
            SplitStringToStringView(str, ',', &tokens);
            EXPECT_TRUE(tokens.size() == 2);
            EXPECT_TRUE(tokens[0] == "");
            EXPECT_TRUE(tokens[1] == "");
        }
        {
            const std::string str = ",,";
            std::vector<StringView> tokens;
            SplitStringToStringView(str, ',', &tokens);
            EXPECT_TRUE(tokens.size() == 3);
            EXPECT_TRUE(tokens[0] == "");
            EXPECT_TRUE(tokens[1] == "");
            EXPECT_TRUE(tokens[2] == "");
        }
        {
            const std::string str = "A,";
            std::vector<StringView> tokens;
            SplitStringToStringView(str, ',', &tokens);
            EXPECT_TRUE(tokens.size() == 2);
            EXPECT_TRUE(tokens[0] == "A");
            EXPECT_TRUE(tokens[1] == "");
        }
        {
            const std::string str = ",B";
            std::vector<StringView> tokens;
            SplitStringToStringView(str, ',', &tokens);
            EXPECT_TRUE(tokens.size() == 2);
            EXPECT_TRUE(tokens[0] == "");
            EXPECT_TRUE(tokens[1] == "B");
        }