带有两个分隔符的字符串流

stringstream with two delimiters

我正在尝试获取一个字符串并首先使用第一个分隔符对其进行分隔,然后使用第二个分隔符 one.I 我正在尝试最大程度地减少使用的资源并避免不必要的循环(然后我又不确定有多少其中可能是必要的)。我也是相当新的 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 何时以及如何中断?

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

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

main.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 函数如此出色的原因在于,假设我有一个字符串模式,并且我想这样消除字符串中的字符集;您可以在上面的主体和 运行 this:

中重复使用相同的向量和字符串
{
    // [...]

    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 抱怨我在 class 中使用包含所有相关静态函数并说请改用命名空间,因为这是 C++ 而不是 Java。就我个人而言,我看不出有什么大不了的,但如果担心的话,你可以删除包装 class 实用程序并将所有通常相关的独立函数放在 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

最后的注释:

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

用户 – Christian Hackl 表示:

"There is no object of Util ever created; it sort of acts as a namespace." - Then use an actual namespace. As I said, this isn't Java.

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

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

这是一个伪例子:

{    // 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)
}

您不能在 class 之外并且只能在命名空间中执行此操作。所以这就是为什么我上面的原始演示中的函数是不可构造的静态方法 class.

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


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

它与 Java 没有任何关系;坦率地说,我从未在 Java 中编写过任何程序。我只用 C 和 C++ 编程过。很多年前,我在上世纪 90 年代末上高中时,可能已经用 Visual Basic 和 C# 开发过一些小型应用程序,但在过去的 20 年里,几乎 95% 都是 C++。你是对的,这是 C++ 而不是 Java。

欢迎来到具有多态行为、循环模板、模板 meta-programming、泛型编程的模板编程世界,同时仍然支持静态类型语言中的过程范式和面向对象范式的角色,现在支持更丰富的特性,例如自动类型推导、lambdas、ranged-based 循环、可变参数模板等等...!