4

我阅读了迭代字符串单词的最优雅方式,并享受答案的简洁性。现在我想对 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

4 回答 4

4

用分隔符分割并返回一个vector<string_view>.

专为快速分割.csv文件中的行而设计。

测试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;
}

小心生命周期:astring_view永远不应该比string它作为窗口的父节点寿命更长。如果父string级超出范围,则string_view指向的内容无效。在这种特殊情况下,API 设计很难出错,因为输入/输出都是string_view父字符串的所有窗口。这最终在内存复制和 CPU 使用方面相当有效。

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

我用它来快速解析一个 .csv 文件。为了获取 .csv 文件中的每一行,我使用istringstreamgetLine()速度极快的方法(约 2GB/秒或单核每秒 1,200,000 行)。

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

// Google Test integrates into VS2017 if ReSharper is installed. 
#include "gtest/gtest.h" // Can install using vcpkg
// In main(), call:   
// ::testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS();

TEST(Strings, Split)
{
    {
        const std::string str = "A,B,C";
        auto tokens = Split(str, ',');
        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";
        auto tokens = Split(str, ',');
        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,";
        auto tokens = Split(str, ',');
        EXPECT_TRUE(tokens.size() == 3);
        EXPECT_TRUE(tokens[0] == "A");
        EXPECT_TRUE(tokens[1] == "B");
        EXPECT_TRUE(tokens[2] == "");
    }
    {
        const std::string str = "";
        auto tokens = Split(str, ',');
        EXPECT_TRUE(tokens.size() == 1);
        EXPECT_TRUE(tokens[0] == "");
    }
    {
        const std::string str =  "A";
        auto tokens = Split(str, ',');
        EXPECT_TRUE(tokens.size() == 1);
        EXPECT_TRUE(tokens[0] == "A");
    }
    {
        const std::string str =  ",";
        auto tokens = Split(str, ',');
        EXPECT_TRUE(tokens.size() == 2);
        EXPECT_TRUE(tokens[0] == "");
        EXPECT_TRUE(tokens[1] == "");
    }
    {
        const std::string str =  ",,";
        auto tokens = Split(str, ',');
        EXPECT_TRUE(tokens.size() == 3);
        EXPECT_TRUE(tokens[0] == "");
        EXPECT_TRUE(tokens[1] == "");
        EXPECT_TRUE(tokens[2] == "");
    }
    {
        const std::string str = "A,";
        auto tokens = Split(str, ',');
        EXPECT_TRUE(tokens.size() == 2);
        EXPECT_TRUE(tokens[0] == "A");
        EXPECT_TRUE(tokens[1] == "");
    }
    {
        const std::string str = ",B";
        auto tokens = Split(str, ',');
        EXPECT_TRUE(tokens.size() == 2);
        EXPECT_TRUE(tokens[0] == "");
        EXPECT_TRUE(tokens[1] == "B");
    }       
}
于 2019-09-22T11:36:46.000 回答
2

如果您想使用该特定方法,您只需将string_viewastring显式转换为:

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等中可用的内容。它们中的任何一个都比这更好。

于 2017-12-28T18:41:22.267 回答
1

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

string_view即使使用基于提议的stream类型,流仍然不是随机访问范围。他们没有办法处理字符串的子范围。这就是为什么他们通过复制而不是通过迭代器或其他方式从流中提取数据的原因。

你想要的最好通过regex基于 - 的机制来完成,因为它不需要复制任何东西。它们与 s 一起工作得很好string_view(尽管您必须string_view手动构建 s )。

于 2017-12-28T18:41:53.023 回答
0

Contango 的回答很好。我稍作改动以适应项目中的字符串和 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");
        }
于 2021-06-25T10:17:20.403 回答