【问题标题】:stringstream with two delimiters带有两个分隔符的字符串流
【发布时间】:2018-03-10 09:11:40
【问题描述】:

我正在尝试获取一个字符串并首先使用第一个定界符然后使用第二个定界符对其进行定界。我试图最小化使用的资源并避免不必要的循环(然后我再次不确定其中有多少是必要的)。我也是相当新的 C++ 程序员。我目前使用的代码如下函数所示。

vector<string> dualDelimit(string str,char del1,char del2)
{
    vector<string> sub;
    stringstream ss(str);
    stringstream ss2;
    while(ss.good())
    {
        string substring;
        getline(ss,substring,del1);

        ss2<<substring;
        while(ss2.good())
        {
            getline(ss2,substring,del2);
            sub.push_back(substring);
        }
    }
    return sub;
}

所以我也是用下面的方法获取输入,调用dualDelimit然后打印出它的内容

void delimitTest()
{
    //variables
    string input;
    char c;
    char d;
    vector<string> result;

    //main program
    cout<<"Insert the string to be delimited: ";
    getline(cin,input);
    cout<<"Insert the delimiter characters separated by whitespace:\t";
    cin>>c>>d;


    result=dualDelimit(input,c,d);

    for(int i=0;i<result.size();i++)
    {
        cout<<result[i]<<endl;
    }
}

假设我使用以下数据:

Insert the string to be delimited: This|is;a|test.
Insert two delimiter characters separated by whitespace:    ; |

打印并存储在结果向量中的结果只有:

This
is

似乎从 ss 获取的第一个字符串然后用第二个分隔符分隔,并且它的子字符串通常添加到向量中,但似乎第一个 while 停止并且不传递其余字符串。 是什么导致了这种行为?函数的第一个 while 何时以及如何中断?

【问题讨论】:

    标签: c++ delimiter stringstream


    【解决方案1】:

    我已经用了很长时间了,效果很好。

    Utility.h

    #ifndef UTILITY_H
    #define UTILITY_H
    
    class Utility {
    public:
        Utility() = delete;
        Utility( const Utility& ) = delete;
        Utility& operator=( const Utility& ) = delete;
    
        static std::string trim( const std::string& str, 
                                 const std::string elementsToTrim = " \t\n\r" ); 
    
        static std::vector<std::string> splitString( const std::string& stringToSplit, 
                                                     const std::string& strDelimiter, 
                                                     const bool keepEmpty = true );    
    };
    
    #endif // !UTILITY_H
    

    Utlity.cpp

    #include "Utility.h"
    
    #include <vector>
    #include <string>
    #include <algorithm>
    
    std::string Utility::trim( const std::string& str, 
                              const std::string elementsToTrim ) {
        std::basic_string<char>::size_type firstIndex = str.find_first_not_of( elementsToTrim );
        if ( firstIndex == std::string::npos )
            return std::string(); // Nothing Left    
        std::basic_string<char>::size_type lastIndex = str.find_last_not_of( elementsToTrim );
        return str.substr( firstIndex, lastIndex - firstIndex + 1 );
    }
    
    std::vector<std::string> Utility::splitString( const std::string& strStringToSplit, 
                                                   const std::string& strDelimiter, 
                                                   const bool keepEmpty ) {
        std::vector<std::string> vResult;
        if ( strDelimiter.empty() ) {
            vResult.push_back( strStringToSplit );
            return vResult;
        }    
        std::string::const_iterator itSubStrStart = strStringToSplit.begin(), itSubStrEnd;
        while ( true ) {
            itSubStrEnd = search( itSubStrStart, strStringToSplit.end(), strDelimiter.begin(), strDelimiter.end() );
            std::string strTemp( itSubStrStart, itSubStrEnd );
            if ( keepEmpty || !strTemp.empty() )
                vResult.push_back( strTemp );
            if ( itSubStrEnd == strStringToSplit.end() )
                break;
            itSubStrStart = itSubStrEnd + strDelimiter.size();
        }    
        return vResult;    
    }
    

    ma​​in.cpp

    #include <iostream>
    #include <vector>
    #include <string>
    
    #include "Utility.h"
    
    int main() {
        std::vector<std::tring> result;
        std::string str( "This|is;a|test." );
        std::cout << str << std::endl;
    
        result = Utility::splitString( str, ";" );
    
        str.clear();
        for ( auto& s : result )
            str += s + " ";
        std::cout << str << std::endl;
    
        result.clear();
        result = Utility::splitString( str, "|" );
    
        str.clear();
        for ( auto& s : result )
            str += s + " ";
        std::cout << str << std::endl;
    
        system( "PAUSE" );
        return 0;
    }
    

    使这个 splitString 函数如此出色的原因在于,假设我有一个字符串模式,并且我想消除字符串中的字符集;您可以在上面的 main 中重用相同的向量和字符串并运行:

    {
        // [...]
    
        str.clear();
        result.clear();
    
        str = std::string( "cruelcruelhellocruelcruelmadryochcruel" );
        result = Utility::splitString( str,  "cruel" );
        str.clear();
        for ( auto& s : result )
            str += s + " ";
        str = Utility::trim( str );
        std::cout << str << std::endl;
    
        system( "PAUSE" );
        return 0;
    }
    

    它可以消除字符串中的字符集或字符串集。快速示例 - 结果。

    test string - wellaaabbbdone

    splitString( s, "a" )   = "well   bbbdone"
                   "aa"     = "well abbbdone"
                   "aaa"    = "well bbbdone";
                   "ab"     = "wellaa bbdone";      
                   "aabb"   = "wella bdone";
                   "aaabbb" = "well done";
    // without calling trim.* This may not be accurate; I did add it & removed it
    // several times; but you should still get the idea.
    

    您可以轻松地将stringstream 替换到此 splitString() 算法中,并在需要时使用其 str() 函数。


    编辑 - 用户:Christian Hackl 抱怨我在一个类中包含所有相关的静态函数,并说要使用命名空间,因为这是 C++ 而不是 Java。就我个人而言,我看不出有什么大不了的,但如果有问题,您可以删除包装类 Utility 并将所有通常相关的独立函数放在 namespace 中:

    // Utility.h
    #ifndef UTILITY_H
    #define UTILITY_H
    
    namespace util {
        // function declarations;
    
    } // namespace util 
    
    #endif // !UTILITY_H 
    
    //===================================
    
    // Utility.cpp
    #include "Utility.h"
    // One of tree ways here:
    
    // First:
    using namespace util;
    
    // function definitions;
    
    // Second:
    util::function definitions
    
    // Third: 
    namespace util {
        // function definitions
    } // namespace util
    

    最后说明:

    在我上面的原始类Utility 中包含用于处理字符串的静态方法,我出于特定原因将它们放在一个类中,而不是仅仅驻留在命名空间中的独立函数。

    用户——Christian Hackl 表示:

    “从未创建过 Util 的对象;它有点像命名空间。” - 然后使用实际的命名空间。正如我所说,这不是 Java。

    我会反对。别弄错我的意思;命名空间是 C++ 的重要组成部分,应该在适当的时候相应地使用。但是,在某些情况下,命名空间不够用,语言和编译器所需的机制需要类或结构的形式。我这是什么意思?这是一个稍微高级一点的主题,但非常简单。你看,在我上面的课程中,我提供了;我只展示了这个类的几个函数。我有大约十几个其他功能,其中一些是功能模板。因此,同样的论点可以说是在命名空间中有独立的函数模板。这不是我班的情况。我的类有几个私有函数模板,用于将字符串作为输入并将其转换为默认的基本类型(int、unsigned、float、double 等)。我什至有将字符串转换为glm::vec2vec3vec4glm::ivec2ivec3ivec4glm::mat3glm::mat4 的函数,它们遵循相同的约定。

    这些转换函数调用一个函数模板来获取该类型的值,但它依赖于函数模板的特化;这不能在命名空间中完成。如果您尝试在没有类或结构的情况下执行此操作;代码将编译,但您将收到链接器错误,因为模板的参数列表不能接受命名空间;但它可以接受类、结构或整数类型。

    这是一个伪例子:

    {    // header file
         ...
         static int converToInt( const std::string& str );
         static float convertToFloat( const std::string& str );
    private:
         template<typename T>
         bool hasValue( const std::string& str, T* pValue );
    
         template<typename T>
         T getValue( const std::string );
    }
    
    
    // *.inl file
    template<typename T>
    bool Utility::hasValue( const std::string& str, T* pValue ) {
        // string manipulation
        pValue[0] = getValue<T>( str );
    
        // check some condition return true or false
    }
    
    // *.cpp file
    int convertToInt( const std::string& str ) {
       // return converted int from string
    }
    
    float convertToFloat( const std::string& str ) {
       // return converted float from string
    }
    
    template<>
    int getValue( const std::string& ) {
        return int that was converted from (convertToInt)
    }
    
    template<>
    float getValue( const std::string& ) {
        return float that was converted from (convertToFloat)
    }
    

    您不能在类之外执行此操作,只能在命名空间中执行此操作。这就是为什么我在原始演示中的上述函数是不可构造类中的静态方法的原因。

    这里需要这种模式的原因是getValue()函数在所有情况下都将字符串作为参数,但唯一的区别是返回类型。在 C++ 和大多数其他语言中,不能单独对返回类型执行重载解析。它最终可能是一个模棱两可的调用,或者如果返回类型从未使用过;编译器甚至可能不会调用该函数。所以为了模拟这个;是函数需要是模板类型的地方;并且它们需要包含在一个类中以专门化这些功能,否则您不能专门化仅位于命名空间中的功能模板。


    关于this is C++ 而不是Java 的整个评论确实是一个糟糕的评估。为什么?不要以任何方式贬低用户的形状或形式,但这种说法似乎完全有偏见。第一的;将静态方法放入没有任何成员的结构或类中并使构造函数私有是完全合法且有效的 C++ 代码。

    它与 Java 无关;坦率地说,我从来没有用 Java 编写过任何东西。我只用 C 和 C++ 编程过。很多年前,当我在 90 年代后期上高中时,我可能已经在 Visual Basic 和 C# 中完成了一些小型应用程序,但在过去的近 20 年中,95% 都是 C++。你是对的,这是 C++ 而不是 Java。

    欢迎来到具有多态行为、循环模板、模板元编程、泛型编程的模板编程世界,同时仍然支持在静态类型语言中既是过程范式又是面向对象范式的角色,现在支持更丰富的功能,例如作为自动类型推导、lambda、基于范围的循环、可变参数模板等等......!

    【讨论】:

    • 您根本不需要 Utility 类。这不是 Java。只需使用非成员函数。
    • 有趣的解决方案。一旦有时间,我会尝试了解幕后发生的一切。
    • @ChristianHackl 我有 Utility 类,因为我有大约 15 - 20 个其他类似的函数,它们可以用于字符串操作。它们都是静态方法,所以我将它们分组到一个 util 类中。它们是一个相当大的项目的一部分。从未创建过 Util 对象;它有点像命名空间。使用 Visual Studio;当我输入Utility:: Intellisense 时,会显示我所有的字符串操作函数的列表。它充当指南。
    • @FrancisCugler: “从未创建过 Util 的对象;它有点像命名空间。” - 然后使用实际的命名空间。正如我所说,这不是 Java。
    • @ChristianHackl 我真的不明白有什么大不了的;我只是将所有常用函数封装到一个类似的抽象类中。为什么这会引起重大关注?
    猜你喜欢
    • 1970-01-01
    • 2018-09-17
    • 2018-01-06
    • 2017-02-10
    • 1970-01-01
    • 2022-12-01
    • 2019-04-05
    • 2014-07-08
    • 1970-01-01
    相关资源
    最近更新 更多