2017-05-12 4 views
5

Ich habe ein Problem, das Strtok löst (Split-Teilzeichenfolgen aus einer Zeichenfolge), aber ich erkennen, dass STRTOK nicht sicher ist. Ich möchte einige modernere Teile der C++ - Standardbibliothek verwenden.Ersetzen Sie eine Schleife mit STRTOK mithilfe von Standard-Bibliothek

Was sollte ich stattdessen verwenden?

static int ParseLine(std::string line, 
        std::string seps, 
        int startIdx, 
        std::vector<CNode>& collection) 
{ 
    if (startIdx > collection.size()) 
    { 
     throw std::invalid_argument("the start index is out of range"); 
    } 
    char buf[2000]; 
    strcpy_s(buf, line.c_str()); 
    auto idx = startIdx; 
    for (auto objectType = strtok(buf, seps.c_str()); objectType != nullptr; idx++) 
    { 
     if (idx == collection.size()) 
     { 
      collection.push_back(CNode(idx)); 
     } 
     collection[idx].SetObjectType(objectType); 
     objectType = strtok(nullptr, seps.c_str()); 
    } 
    return (idx - 1); 
} 

die komplette Probe, die mit _CRT_SECURE_NO_WARNINGS kompiliert:

#include <string> 
#include <vector> 
#include <iostream> 

class CObject 
{ 
    std::string _objectType; 

public: 
       CObject() : _objectType("n/a") {} 
    void  SetObjectType(std::string objectType) { _objectType = objectType; } 
    std::string GetObjectType() const { return _objectType; } 

}; 

class CNode 
{ 
    int  _id; 
    CObject _object; 

public: 
    explicit CNode(int id) : _id(id) {} 
    void  SetObjectType(std::string objectType) { _object.SetObjectType(objectType); } 
    std::string GetObjectType() const { return _object.GetObjectType(); } 

}; 

// Update the collection of nodes with the object types found in the line specified. 
// Return the number of elements in the collection. 
static int ParseLine(std::string line, std::string seps, int startIdx, std::vector<CNode>& collection) 
{ 
    if (startIdx > collection.size()) 
    { 
     throw std::invalid_argument("the start index is out of range"); 
    } 
    char buf[2000]; 
    strcpy_s(buf, line.c_str()); 
    auto idx = startIdx; 
    for (auto objectType = strtok(buf, seps.c_str()); objectType != nullptr; idx++) 
    { 
     if (idx == collection.size()) 
     { 
      collection.push_back(CNode(idx)); 
     } 
     collection[idx].SetObjectType(objectType); 
     objectType = strtok(nullptr, seps.c_str()); 
    } 
    return (idx - 1); 
} 

int main() 
{ 
    std::string seps = "."; 
    //     2 3 4 5 6 7 8 9 
    std::string line = "abc.def.ghi.klm.nop.qrs.tuv.wxyz"; 
    std::vector<CNode> collection { CNode(0), CNode(1), CNode(2) , CNode(3) , CNode(4) , CNode(5) }; 
    auto startAt = 2; 
    try 
    { 
     auto collection_size = ParseLine(line, seps, startAt, collection); 
     std::cout << collection_size << std::endl; 
     for (auto value : collection) 
     { 
      std::cout << value.GetObjectType() << std::endl; 
     } 
    } 
    catch (std::invalid_argument& e) 
    { 
     std::cout << " out of range exception " << e.what() << std::endl; 
    } 
    return 0; 
} 
+0

Wie komfortabel sind Sie mit regulären Ausdrücken? – Charles

+0

Danke für den Hinweis, ich habe nie regulären Ausdruck verwendet, aber ich werde nach einer Lösung in dieser Richtung graben. –

Antwort

3

Hier sind zwei Beispiele für eine getrennte Zeichenfolge zu splitten.

Die erste verwendet std::getline mit einem Zeichenfolge-Stream, der ein Trennzeichen anstelle des Standard-Newline-Zeichens angibt. Bei dieser Technik können nur Einzelzeichentrennzeichen verwendet werden.

Das zweite Beispiel verwendet die Bibliothek <regex>, die Trennzeichen beliebiger Länge ermöglicht und Ihnen außerdem mehr Kontrolle darüber gibt, wie ein Trennzeichen erkannt wird. Beachten Sie, dass das Punktzeichen in der Regex-Spezifikation maskiert werden muss, da in der Regex-Sprache "." fungiert als Platzhalter.

std::vector<std::string> SplitLine(std::string const& line, std::string seps) 
{ 
    std::regex regxSeps(seps); // the dot character needs to be escaped in a regex 
    std::sregex_token_iterator rit(line.begin(), line.end(), regxSeps, -1); 
    return std::vector<std::string>(rit, std::sregex_token_iterator()); 
} 

static int ParseLine(std::string line, std::string seps, size_t startIdx, std::vector<CNode>& collection) 
{ 
    if (startIdx > collection.size()) 
    { 
     throw std::invalid_argument("the start index is out of range"); 
    } 

    auto objectTypes = SplitLine(line, seps); 

    auto idx = startIdx; 
    for (const auto& objectType : objectTypes) 
    { 
     if (idx == collection.size()) 
     { 
      collection.push_back(CNode(idx)); 
     } 
     collection[idx++].SetObjectType(objectType); 
    } 
    return (idx - 1); 
} 

int main() 
{ 
    std::string seps = "\\."; // the dot character needs to be escaped in a regex 
    ... 
} 
+0

Danke, Ihre Lösung mit Regex ist kurz und zeigt einen klaren und praktischen Fall, wie man strtok durch die Standardbibliothek ersetzen kann. –

0

Unterhalb der Lösung strtok mit der Standardbibliothek (mit der Antwort erhalten) zu ersetzen Diese Dienstprogrammklasse. Ich benutze dies für verschiedene Methoden der Arbeit mit Strings aus der Umwandlung, um Leerraum, Spalten zu entfernen, zu ändern Fälle usw. Hier eine Funktion ist eine Zeichenfolge aus dieser Klasse zu spalten:

Utility.h

class Utility { 
public: 
    static std::vector<std::string> splitString(const std::string& strStringToSplit, 
               const std::string& strDelimiter, 
               const bool keepEmpty = true); 

private: 
    Utility(); 
}; 

Utility.cpp

#include "Utility.h" 

// splitString() 
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; 

} // splitString 

Die benötigte Bibliothek für dieses Dienstprogramm Verfahren umfasst zu arbeiten sind <vector>, <string> und <algorithm>, die meistens in fast allen Anwendungen verwendet werden.

diese Funktion nutzen wir einen einfachen Test, wie dies tun:

#include <iostream> 
#include <string> 
#include <vector> 
#include <algorithm> 

#include "Utility.h" 

int main() { 
    std::string someLongString2("Hello World How Are You"); 

    std::vector<std::string> singleWords; 
    singleWords = Utility::splitString(someLongString, " "); 

    // Space is the delimiter and now each individual word 
    // from the long string are now each a new string stored 
    // in this vector. You can use any character for your delimiter. 
    // Also this function is not limited to having a single character 
    // as its delimiter. You can use a series of characters or specific 
    // words as your delimiter. Such as a comma followed by a space. 

     std::string someLongString2("Hello, World, How, Are, You"); 
     singleWords.clear(); 
     singleWords = Utility::splitString(someLongString2, ", "); 

    return 0; 
} // main 
+0

Kennt jemand eine Möglichkeit, die Schleife durch Aufrufe der Standardbibliothek zu ersetzen? Ich habe gehört, dass wir die meiste Zeit Loops durch Aufrufe von Std ersetzen sollten. –

1

Ich habe eine Utility-Klasse, die nichts anderes als statische Methoden hat, wie Sie nicht eine Instanz erstellen:

#include <iostream> 
#include <sstream> 
#include <vector> 
#include <regex> 

std::vector<std::string> GetlineSplit(std::string const& line) { 
    static const char sep = '.'; 
    std::istringstream liness{line}; 
    std::vector<std::string> fields; 
    for(std::string field; std::getline(liness, field, sep);) { 
     fields.push_back(field); 
    } 
    return fields; 
} 

std::vector<std::string> RegexSplit(std::string const& line) { 
    std::regex seps("\\."); // the dot character needs to be escaped in a regex 
    std::sregex_token_iterator rit(line.begin(), line.end(), seps, -1); 
    return std::vector<std::string>(rit, std::sregex_token_iterator()); 
} 

int main() { 
    std::string line = "abc.def.ghi.klm.nop.qrs.tuv.wxyz"; 

    std::cout << "getline split result:\n"; 
    auto fields_getline = GetlineSplit(line); 
    for(const auto& field : fields_getline) { 
     std::cout << field << '\n'; 
    } 

    std::cout << "\nregex split result:\n"; 
    auto fields_regex = RegexSplit(line); 
    for(const auto& field : fields_regex) { 
     std::cout << field << '\n'; 
    } 
} 
+1

Ich mag Ihre Utility :: SplitString() -Methode. Vielen Dank. –

+0

@LessWhite gut vielen Dank; Ich habe es technisch nicht geschrieben; aber es wurde mir vor weiteren Jahren gezeigt, als ich VS2010 oder 12 benutzte und seitdem in meiner Sammlung war, und ich benutze diese statische Klasse die ganze Zeit: daher wiederverwendbar :). Ich habe ungefähr 10 bis 20 Methoden in dieser Utility-Klasse, die sich als praktisch erweisen, ohne sie immer wieder neu schreiben zu müssen. Einige wurden von mir selbst geschrieben, während andere von anderen geschrieben wurden.Wenn ich mich erinnern könnte, wer mir diese Funktion gezeigt hat; Ich würde ihnen dafür danken, aber ich möchte anderen immer noch in ihrer Notlage helfen. –

Verwandte Themen