如何在 C++ 中标记字符串?

Posted

技术标签:

【中文标题】如何在 C++ 中标记字符串?【英文标题】:How do I tokenize a string in C++? 【发布时间】:2010-09-08 09:43:58 【问题描述】:

Java 有一个方便的拆分方法:

String str = "The quick brown fox";
String[] results = str.split(" ");

在 C++ 中有没有简单的方法来做到这一点?

【问题讨论】:

我不敢相信这个例行任务在 c++ 中是如此令人头疼 这在 C++ 中并不令人头疼 - 有多种方法可以实现它。与 C# 相比,程序员对 C++ 的了解更少——它是关于营销和投资的……查看各种 C++ 选项以实现相同目标:cplusplus.com/faq/sequences/strings/split @hB0 经历了很多问题的答案,但仍然没有决定手段是一件令人头疼的事情。一个需要那个库,另一个只用于空间,另一个不处理空间.. Split a string in C++?的可能重复 为什么 C++ 中的一切都必须是一场斗争? 【参考方案1】:

这是一个示例标记器类,它可能会执行您想要的操作

//Header file
class Tokenizer 

    public:
        static const std::string DELIMITERS;
        Tokenizer(const std::string& str);
        Tokenizer(const std::string& str, const std::string& delimiters);
        bool NextToken();
        bool NextToken(const std::string& delimiters);
        const std::string GetToken() const;
        void Reset();
    protected:
        size_t m_offset;
        const std::string m_string;
        std::string m_token;
        std::string m_delimiters;
;

//CPP file
const std::string Tokenizer::DELIMITERS(" \t\n\r");

Tokenizer::Tokenizer(const std::string& s) :
    m_string(s), 
    m_offset(0), 
    m_delimiters(DELIMITERS) 

Tokenizer::Tokenizer(const std::string& s, const std::string& delimiters) :
    m_string(s), 
    m_offset(0), 
    m_delimiters(delimiters) 

bool Tokenizer::NextToken() 

    return NextToken(m_delimiters);


bool Tokenizer::NextToken(const std::string& delimiters) 

    size_t i = m_string.find_first_not_of(delimiters, m_offset);
    if (std::string::npos == i) 
    
        m_offset = m_string.length();
        return false;
    

    size_t j = m_string.find_first_of(delimiters, i);
    if (std::string::npos == j) 
    
        m_token = m_string.substr(i);
        m_offset = m_string.length();
        return true;
    

    m_token = m_string.substr(i, j - i);
    m_offset = j;
    return true;

例子:

std::vector <std::string> v;
Tokenizer s("split this string", " ");
while (s.NextToken())

    v.push_back(s.GetToken());

【讨论】:

【参考方案2】:

C++ 标准库算法非常普遍地基于迭代器而不是具体容器。不幸的是,这使得在 C++ 标准库中提供类似 Java 的 split 函数变得很困难,尽管没有人认为这会很方便。但它的返回类型是什么? std::vector&lt;std::basic_string&lt;…&gt;&gt;?也许可以,但是我们不得不执行(可能是冗余和昂贵的)分配。

相反,C++ 提供了多种基于任意复杂分隔符分割字符串的方法,但没有一种方法像其他语言那样封装得很好。多种方式fill whole blog posts.

最简单的方法是,您可以使用std::string::find 进行迭代,直到您点击std::string::npos,然后使用std::string::substr 提取内容。

在空格上分割的更流畅(和惯用但基本)的版本将使用std::istringstream

auto iss = std::istringstream"The quick brown fox";
auto str = std::string;

while (iss >> str) 
    process(str);

使用std::istream_iterators,也可以使用其迭代器范围构造函数将字符串流的内容复制到向量中。

多个库(例如Boost.Tokenizer)提供特定的标记器。

更高级的拆分需要正则表达式。 C++为此特别提供了std::regex_token_iterator

auto const str = "The quick brown fox"s;
auto const re = std::regexR"(\s+)";
auto const vec = std::vector<std::string>(
    std::sregex_token_iteratorbegin(str), end(str), re, -1,
    std::sregex_token_iterator
);

【讨论】:

遗憾的是,boost 并非总是适用于所有项目。我将不得不寻找一个非提升的答案。 并非每个项目都对“开源”开放。我在受到严格监管的行业工作。这不是问题,真的。这只是生活中的事实。 Boost 并非随处可用。 @NonlinearIdeas 另一个问题/答案根本与开源项目无关。 any 项目也是如此。也就是说,我当然了解 MISRA C 之类的受限标准,但也可以理解,无论如何你都是从头开始构建所有东西的(除非你碰巧找到了一个兼容的库——这很少见)。无论如何,重点不是“Boost is not available”——而是你有特殊要求,几乎任何通用答案都不适合。 @NonlinearIdeas 例如,其他非 Boost 答案也不符合 MISRA。 @Dmitry 什么是“STL barf”?!整个社区都非常支持替换 C 预处理器——事实上,有建议这样做。但是您建议使用 php 或其他语言代替将是一个巨大的倒退。【参考方案3】:

如果您愿意使用 C,可以使用 strtok 函数。使用时要注意多线程问题。

【讨论】:

请注意,strtok 修改了您正在检查的字符串,因此您不能在不复制的情况下在 const char * 字符串上使用它。 多线程问题是 strtok 使用一个全局变量来跟踪它的位置,所以如果你有两个线程都使用 strtok,你会得到未定义的行为。 @JohnMcG 或者只使用strtok_s,它基本上是strtok,带有显式状态传递。【参考方案4】:

这是一个非常简单的:

#include <vector>
#include <string>
using namespace std;

vector<string> split(const char *str, char c = ' ')

    vector<string> result;

    do
    
        const char *begin = str;

        while(*str != c && *str)
            str++;

        result.push_back(string(begin, str));
     while (0 != *str++);

    return result;

【讨论】:

我需要在.h文件中添加这个方法的原型吗? 这并不完全是“最佳”答案,因为它仍然使用字符串文字,它是纯 C 常量字符数组。我相信提问者在问他是否可以对后者引入的“字符串”类型的 C++ 字符串进行标记。 这需要一个新的答案,因为我强烈怀疑在 C++11 中包含正则表达式已经改变了最佳答案。 对于这个答案,第一个/最后一个字符等于分隔符的字符串有问题。例如字符串 "a" 结果是 [" ", "a"]。【参考方案5】:

我认为这就是字符串流上的 &gt;&gt; 运算符的用途:

string word; sin >> word;

【讨论】:

我给出了一个糟糕(太简单)的例子是我的错。据我所知,仅当您的分隔符为空格时才有效。【参考方案6】:

您可以使用流、迭代器和复制算法相当直接地执行此操作。

#include <string>
#include <vector>
#include <iostream>
#include <istream>
#include <ostream>
#include <iterator>
#include <sstream>
#include <algorithm>

int main()

  std::string str = "The quick brown fox";

  // construct a stream from the string
  std::stringstream strstr(str);

  // use stream iterators to copy the stream to the vector as whitespace separated strings
  std::istream_iterator<std::string> it(strstr);
  std::istream_iterator<std::string> end;
  std::vector<std::string> results(it, end);

  // send the vector to stdout.
  std::ostream_iterator<std::string> oit(std::cout);
  std::copy(results.begin(), results.end(), oit);

【讨论】:

我觉得那些 std:: 读起来很烦人。为什么不使用“使用”? @Vadi:因为编辑别人的帖子非常麻烦。 @pheze:我更喜欢让std 这样我知道我的对象来自哪里,这只是风格问题。 我理解你的理由,如果它适合你,我认为这实际上是一个不错的选择,但从教学的角度来看,我实际上同意 pheze。更容易阅读和理解一个完全陌生的例子,比如这个在顶部带有“using namespace std”的例子,因为它需要更少的努力来解释以下几行......尤其是在这种情况下,因为一切都来自标准库。您可以通过一系列“使用 std::string;”使对象的来源变得容易阅读和明显。等等。特别是因为功能很短。 尽管“std::”前缀令人讨厌或难看,但最好将它们包含在示例代码中,以便完全清楚这些函数的来源。如果它们打扰了您,那么在您窃取示例并将其声明为您自己的示例后,将它们替换为“使用”是微不足道的。 是的!他说了什么!最佳实践是使用 std 前缀。毫无疑问,任何大型代码库都将拥有自己的库和命名空间,当您开始引发命名空间冲突时,使用“using namespace std”会让您头疼。【参考方案7】:

使用 strtok。在我看来,没有必要围绕标记化构建一个类,除非 strtok 没有为您提供所需的东西。可能不会,但在用 C 和 C++ 编写各种解析代码的 15 年多的时间里,我一直使用 strtok。这是一个例子

char myString[] = "The quick brown fox";
char *p = strtok(myString, " ");
while (p) 
    printf ("Token: %s\n", p);
    p = strtok(NULL, " ");

一些注意事项(可能不适合您的需要)。该字符串在此过程中被“销毁”,这意味着 EOS 字符被内嵌在分隔符点中。正确使用可能需要您制作字符串的非常量版本。您还可以在解析中更改分隔符列表。

在我个人看来,上面的代码比为它编写一个单独的类要简单得多,也更容易使用。对我来说,这是该语言提供的功能之一,它做得很好而且干净。它只是一个“基于 C”的解决方案。合适,简单,不用写很多额外的代码:-)

【讨论】:

并不是我不喜欢 C,但是 strtok 不是线程安全的,你需要确定你发送给它的字符串包含一个空字符以避免可能的缓冲区溢出。 有 strtok_r,但这是一个 C++ 问题。 @tloach:在 MS C++ 编译器中,strtok 是线程安全的,因为内部静态变量是在 TLS(线程本地存储)上创建的(实际上它取决于编译器) @ahmed:线程安全不仅仅意味着能够在不同的线程中运行该函数两次。在这种情况下,如果在 strtok 运行时修改了线程,则可以在 strtok 的整个运行期间使字符串有效,但 strtok 仍然会因为字符串更改而搞砸,它现在已经超过了空字符,并且它将继续读取内存,直到它违反安全规定或找到一个空字符。这是原始 C 字符串函数的问题,如果您没有在某个地方指定长度会遇到问题。 strtok 需要一个指向非 const 以 null 结尾的字符数组的指针,这在 c++ 代码中并不常见……您最喜欢从 std:: 转换为它的方法是什么字符串?【参考方案8】:

Boost tokenizer 类可以让这种事情变得非常简单:

#include <iostream>
#include <string>
#include <boost/foreach.hpp>
#include <boost/tokenizer.hpp>

using namespace std;
using namespace boost;

int main(int, char**)

    string text = "token, test   string";

    char_separator<char> sep(", ");
    tokenizer< char_separator<char> > tokens(text, sep);
    BOOST_FOREACH (const string& t, tokens) 
        cout << t << "." << endl;
    

针对 C++11 更新:

#include <iostream>
#include <string>
#include <boost/tokenizer.hpp>

using namespace std;
using namespace boost;

int main(int, char**)

    string text = "token, test   string";

    char_separator<char> sep(", ");
    tokenizer<char_separator<char>> tokens(text, sep);
    for (const auto& t : tokens) 
        cout << t << "." << endl;
    

【讨论】:

好东西,我最近用过这个。我的 Visual Studio 编译器有一个奇怪的抱怨,直到我使用空格分隔标记(文本,s​​ep)位之前的两个“>”字符:(错误 C2947:期望 '>' 终止模板参数列表,发现 '> >') @AndyUK 是的,没有空格,编译器会将其解析为提取运算符而不是两个关闭模板。 理论上已经在 C++0x 中修复了 注意char_separator构造函数的第三个参数(drop_empty_tokens是默认值,替代是keep_empty_tokens)。 @puk - 它是 C++ 头文件的常用后缀。 (如 .h 用于 C 标头)【参考方案9】:

Boost具有强大的拆分功能:boost::algorithm::split。

示例程序:

#include <vector>
#include <boost/algorithm/string.hpp>

int main() 
    auto s = "a,b, c ,,e,f,";
    std::vector<std::string> fields;
    boost::split(fields, s, boost::is_any_of(","));
    for (const auto& field : fields)
        std::cout << "\"" << field << "\"\n";
    return 0;

输出:

"a"
"b"
" c "
""
"e"
"f"
""

【讨论】:

【参考方案10】:

对于简单的东西,我只使用以下内容:

unsigned TokenizeString(const std::string& i_source,
                        const std::string& i_seperators,
                        bool i_discard_empty_tokens,
                        std::vector<std::string>& o_tokens)

    unsigned prev_pos = 0;
    unsigned pos = 0;
    unsigned number_of_tokens = 0;
    o_tokens.clear();
    pos = i_source.find_first_of(i_seperators, pos);
    while (pos != std::string::npos)
    
        std::string token = i_source.substr(prev_pos, pos - prev_pos);
        if (!i_discard_empty_tokens || token != "")
        
            o_tokens.push_back(i_source.substr(prev_pos, pos - prev_pos));
            number_of_tokens++;
        

        pos++;
        prev_pos = pos;
        pos = i_source.find_first_of(i_seperators, pos);
    

    if (prev_pos < i_source.length())
    
        o_tokens.push_back(i_source.substr(prev_pos));
        number_of_tokens++;
    

    return number_of_tokens;

怯懦的免责声明:我编写实时数据处理软件,其中数据通过二进制文件、套接字或某些 API 调用(I/O 卡、相机)进入。我从不将此功能用于比在启动时读取外部配置文件更复杂或时间紧迫的事情。

【讨论】:

【参考方案11】:

没有冒犯,但是对于这样一个简单的问题,你让事情太复杂了。使用Boost 的原因有很多。但是对于这么简单的事情,就像用 20# 的雪橇打苍蝇一样。

void
split( vector<string> & theStringVector,  /* Altered/returned value */
       const  string  & theString,
       const  string  & theDelimiter)

    UASSERT( theDelimiter.size(), >, 0); // My own ASSERT macro.

    size_t  start = 0, end = 0;

    while ( end != string::npos)
    
        end = theString.find( theDelimiter, start);

        // If at end, use length=maxLength.  Else use length=end-start.
        theStringVector.push_back( theString.substr( start,
                       (end == string::npos) ? string::npos : end - start));

        // If at end, use start=maxSize.  Else use start=end+delimiter.
        start = (   ( end > (string::npos - theDelimiter.size()) )
                  ?  string::npos  :  end + theDelimiter.size());
    

例如(对于 Doug 的情况),

#define SHOW(I,X)   cout << "[" << (I) << "]\t " # X " = \"" << (X) << "\"" << endl

int
main()

    vector<string> v;

    split( v, "A:PEP:909:Inventory Item", ":" );

    for (unsigned int i = 0;  i < v.size();   i++)
        SHOW( i, v[i] );

是的,我们可以让 split() 返回一个新向量而不是传入一个向量。包装和重载很简单。但取决于我在做什么,我经常发现重用预先存在的对象比总是创建新对象更好。 (只要我不忘记清空中间的向量!)

参考:http://www.cplusplus.com/reference/string/string/。

(我最初是在写对 Doug 问题的回复:C++ Strings Modifying and Extracting based on Separators (closed)。但由于 Martin York 在这里用指针结束了这个问题......我将概括我的代码。)

【讨论】:

为什么要定义一个只在一个地方使用的宏。以及您的 UASSERT 比标准断言更好。像这样将比较分成 3 个标记除了需要比您原本需要的更多的逗号之外没有任何作用。 也许 UASSERT 宏显示(在错误消息中)两个比较值之间(和值)的实际关系?恕我直言,这实际上是个好主意。 呃,std::string 类为什么不包含 split() 函数? 我认为while循环的最后一行应该是start = ((end &gt; (theString.size() - theDelimiter.size())) ? string::npos : end + theDelimiter.size());,while循环应该是while (start != string::npos)。另外,在将子字符串插入向量之前,我会检查子字符串以确保它不为空。 @JohnK 如果输入有两个连续的分隔符,那么显然它们之间的字符串是空的,应该插入到向量中。如果空值对于特定目的是不可接受的,那是另一回事,但恕我直言,这种约束应该在这种非常通用的功能之外强制执行。【参考方案12】:

另一种快捷方式是使用getline。比如:

stringstream ss("bla bla");
string s;

while (getline(ss, s, ' ')) 
 cout << s << endl;

如果你愿意,你可以创建一个简单的split() 方法返回一个vector&lt;string&gt;,即 真的很有用。

【讨论】:

我在使用这种技术时遇到了问题,字符串中有 0x0A 个字符,导致 while 循环过早退出。否则,这是一个很好的简单快速的解决方案。 这很好,但请记住,这样做不会考虑默认分隔符 '\n'。这个例子可以工作,但如果你使用类似:while(getline(inFile,word,' ')) inFile 是包含多行的 ifstream 对象,你会得到有趣的结果.. 太糟糕了 getline 返回的是流而不是字符串,使得它在没有临时存储的初始化列表中无法使用 酷!没有 boost 和 C++11,是那些遗留项目的好解决方案! 答案是这样,只是函数名有点别扭。【参考方案13】:

MFC/ATL 有一个非常好的分词器。来自 MSDN:

CAtlString str( "%First Second#Third" );
CAtlString resToken;
int curPos= 0;

resToken= str.Tokenize("% #",curPos);
while (resToken != "")

   printf("Resulting token: %s\n", resToken);
   resToken= str.Tokenize("% #",curPos);
;

Output

Resulting Token: First
Resulting Token: Second
Resulting Token: Third

【讨论】:

这个 Tokenize() 函数会跳过空标记,例如,如果主字符串中有子字符串“%%”,则不返回空标记。它被跳过了。【参考方案14】:

我知道您要求提供 C++ 解决方案,但您可能认为这很有帮助:

Qt

#include <QString>

...

QString str = "The quick brown fox"; 
QStringList results = str.split(" "); 

在此示例中,与 Boost 相比的优势在于它可以直接一对一地映射到您的帖子代码。

在Qt documentation查看更多信息

【讨论】:

【参考方案15】:

检查这个例子。它可能会帮助你..

#include <iostream>
#include <sstream>

using namespace std;

int main ()

    string tmps;
    istringstream is ("the dellimiter is the space");
    while (is.good ()) 
        is >> tmps;
        cout << tmps << "\n";
    
    return 0;

【讨论】:

我愿意while ( is &gt;&gt; tmps ) std::cout &lt;&lt; tmps &lt;&lt; "\n"; 【参考方案16】:

您可以简单地使用 regular expression library 并使用正则表达式解决该问题。

使用表达式 (\w+) 和 \1 中的变量(或 $1,具体取决于正则表达式的库实现)。

【讨论】:

+1 用于建议正则表达式,如果您不需要扭曲速度,它是最灵活的解决方案,尚未在所有地方都支持,但随着时间的推移,这将变得不那么重要。 +1 来自我,刚刚在 c++11 中尝试了 。如此简单优雅【参考方案17】:

如果要标记化的输入字符串的最大长度已知,则可以利用这一点并实现一个非常快速的版本。我正在勾画下面的基本思想,它的灵感来自于 strtok() 和“后缀数组”——Jon Bentley 的“Programming Perls”第 2 版第 15 章中描述的数据结构。在这种情况下,C++ 类只提供了一些组织和便利使用。所示的实现可以轻松扩展以删除标记中的前导和尾随空白字符。

基本上,可以将分隔符替换为以字符串结尾的“\0”字符,并使用修改后的字符串设置指向标记的指针。在字符串仅由分隔符组成的极端情况下,一个字符串长度加上 1 产生的空标记。复制要修改的字符串很实用。

头文件:

class TextLineSplitter

public:

    TextLineSplitter( const size_t max_line_len );

    ~TextLineSplitter();

    void            SplitLine( const char *line,
                               const char sep_char = ',',
                             );

    inline size_t   NumTokens( void ) const
    
        return mNumTokens;
    

    const char *    GetToken( const size_t token_idx ) const
    
        assert( token_idx < mNumTokens );
        return mTokens[ token_idx ];
    

private:
    const size_t    mStorageSize;

    char           *mBuff;
    char          **mTokens;
    size_t          mNumTokens;

    inline void     ResetContent( void )
    
        memset( mBuff, 0, mStorageSize );
        // mark all items as empty:
        memset( mTokens, 0, mStorageSize * sizeof( char* ) );
        // reset counter for found items:
        mNumTokens = 0L;
    
;

实现文件:

TextLineSplitter::TextLineSplitter( const size_t max_line_len ):
    mStorageSize ( max_line_len + 1L )

    // allocate memory
    mBuff   = new char  [ mStorageSize ];
    mTokens = new char* [ mStorageSize ];

    ResetContent();


TextLineSplitter::~TextLineSplitter()

    delete [] mBuff;
    delete [] mTokens;



void TextLineSplitter::SplitLine( const char *line,
                                  const char sep_char   /* = ',' */,
                                )

    assert( sep_char != '\0' );

    ResetContent();
    strncpy( mBuff, line, mMaxLineLen );

    size_t idx       = 0L; // running index for characters

    do
    
        assert( idx < mStorageSize );

        const char chr = line[ idx ]; // retrieve current character

        if( mTokens[ mNumTokens ] == NULL )
        
            mTokens[ mNumTokens ] = &mBuff[ idx ];
         // if

        if( chr == sep_char || chr == '\0' )
         // item or line finished
            // overwrite separator with a 0-terminating character:
            mBuff[ idx ] = '\0';
            // count-up items:
            mNumTokens ++;
         // if

     while( line[ idx++ ] );

一个使用场景是:

// create an instance capable of splitting strings up to 1000 chars long:
TextLineSplitter spl( 1000 );
spl.SplitLine( "Item1,,Item2,Item3" );
for( size_t i = 0; i < spl.NumTokens(); i++ )

    printf( "%s\n", spl.GetToken( i ) );

输出:

Item1

Item2
Item3

【讨论】:

【参考方案18】:

您可以利用 boost::make_find_iterator。类似的东西:

template<typename CH>
inline vector< basic_string<CH> > tokenize(
    const basic_string<CH> &Input,
    const basic_string<CH> &Delimiter,
    bool remove_empty_token
    ) 

    typedef typename basic_string<CH>::const_iterator string_iterator_t;
    typedef boost::find_iterator< string_iterator_t > string_find_iterator_t;

    vector< basic_string<CH> > Result;
    string_iterator_t it = Input.begin();
    string_iterator_t it_end = Input.end();
    for(string_find_iterator_t i = boost::make_find_iterator(Input, boost::first_finder(Delimiter, boost::is_equal()));
        i != string_find_iterator_t();
        ++i) 
        if(remove_empty_token)
            if(it != i->begin())
                Result.push_back(basic_string<CH>(it,i->begin()));
        
        else
            Result.push_back(basic_string<CH>(it,i->begin()));
        it = i->end();
    
    if(it != it_end)
        Result.push_back(basic_string<CH>(it,it_end));

    return Result;

【讨论】:

【参考方案19】:

pystring 是一个小库,它实现了一堆 Python 的字符串函数,包括 split 方法:

#include <string>
#include <vector>
#include "pystring.h"

std::vector<std::string> chunks;
pystring::split("this string", chunks);

// also can specify a separator
pystring::split("this-string", chunks, "-");

【讨论】:

哇,您已经回答了我的当前问题和许多未来的问题。我知道c ++很强大。但是,当拆分字符串导致源代码像上面的答案时,这显然令人沮丧。我很想知道像这样的其他库,它们会降低更高级别的语言便利性。 哇,你真的让我很开心!!不知道pystring。这将节省我很多时间!【参考方案20】:

boost::tokenizer 是您的朋友,但请考虑通过使用 wstring/wchar_t 而不是旧的 string/char 类型来使您的代码可移植到国际化 (i18n) 问题。

#include <iostream>
#include <boost/tokenizer.hpp>
#include <string>

using namespace std;
using namespace boost;

typedef tokenizer<char_separator<wchar_t>,
                  wstring::const_iterator, wstring> Tok;

int main()

  wstring s;
  while (getline(wcin, s)) 
    char_separator<wchar_t> sep(L" "); // list of separator characters
    Tok tok(s, sep);
    for (Tok::iterator beg = tok.begin(); beg != tok.end(); ++beg) 
      wcout << *beg << L"\t"; // output (or store in vector)
    
    wcout << L"\n";
  
  return 0;

【讨论】:

"legacy" 绝对不正确,wchar_t 是一种可怕的依赖于实现的类型,除非绝对必要,否则任何人都不应使用。 使用 wchar_t 并不能自动解决任何 i18n 问题。您使用编码来解决该问题。如果您通过分隔符拆分字符串,则暗示分隔符不会与字符串内任何标记的编码内容发生冲突。可能需要转义,等等。wchar_t 并不是解决这个问题的神奇方法。【参考方案21】:

这里有许多过于复杂的建议。试试这个简单的 std::string 解决方案:

using namespace std;

string someText = ...

string::size_type tokenOff = 0, sepOff = tokenOff;
while (sepOff != string::npos)

    sepOff = someText.find(' ', sepOff);
    string::size_type tokenLen = (sepOff == string::npos) ? sepOff : sepOff++ - tokenOff;
    string token = someText.substr(tokenOff, tokenLen);
    if (!token.empty())
        /* do something with token */;
    tokenOff = sepOff;

【讨论】:

【参考方案22】:

这是一种允许您控制是否包含空标记(如 strsep)或排除(如 strtok)的方法。

#include <string.h> // for strchr and strlen

/*
 * want_empty_tokens==true  : include empty tokens, like strsep()
 * want_empty_tokens==false : exclude empty tokens, like strtok()
 */
std::vector<std::string> tokenize(const char* src,
                                  char delim,
                                  bool want_empty_tokens)

  std::vector<std::string> tokens;

  if (src and *src != '\0') // defensive
    while( true )  
      const char* d = strchr(src, delim);
      size_t len = (d)? d-src : strlen(src);

      if (len or want_empty_tokens)
        tokens.push_back( std::string(src, len) ); // capture token

      if (d) src += len+1; else break;
    

  return tokens;

【讨论】:

【参考方案23】:

这是一个仅使用标准库文件标记化的简单循环

#include <iostream.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <conio.h>
class word
    
     public:
     char w[20];
     word()
      
        for(int j=0;j<=20;j++)
        w[j]='\0';
      
   



;

void main()
  
    int i=1,n=0,j=0,k=0,m=1;
    char input[100];
    word ww[100];
    gets(input);

    n=strlen(input);


    for(i=0;i<=m;i++)
      
        if(context[i]!=' ')
         
            ww[k].w[j]=context[i];
            j++;

         
         else
        
         k++;
         j=0;
         m++;
        

   
 

【讨论】:

【参考方案24】:

简单的 C++ 代码(标准 C++98),接受多个分隔符(在 std::string 中指定),仅使用向量、字符串和迭代器。

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

std::vector<std::string> 
split(const std::string& str, const std::string& delim)
    std::vector<std::string> result;
    if (str.empty())
        throw std::runtime_error("Can not tokenize an empty string!");
    std::string::const_iterator begin, str_it;
    begin = str_it = str.begin(); 
    do 
        while (delim.find(*str_it) == std::string::npos && str_it != str.end())
            str_it++; // find the position of the first delimiter in str
        std::string token = std::string(begin, str_it); // grab the token
        if (!token.empty()) // empty token only when str starts with a delimiter
            result.push_back(token); // push the token into a vector<string>
        while (delim.find(*str_it) != std::string::npos && str_it != str.end())
            str_it++; // ignore the additional consecutive delimiters
        begin = str_it; // process the remaining tokens
         while (str_it != str.end());
    return result;


int main() 
    std::string test_string = ".this is.a.../.simple;;test;;;END";
    std::string delim = "; ./"; // string containing the delimiters
    std::vector<std::string> tokens = split(test_string, delim);           
    for (std::vector<std::string>::const_iterator it = tokens.begin(); 
        it != tokens.end(); it++)
            std::cout << *it << std::endl;

【讨论】:

【参考方案25】:

我针对类似问题发布了此答案。 不要重新发明***。我使用了许多库,我遇到的最快和最灵活的是:C++ String Toolkit Library。

这是我在 *** 其他地方发布的如何使用它的示例。

#include <iostream>
#include <vector>
#include <string>
#include <strtk.hpp>

const char *whitespace  = " \t\r\n\f";
const char *whitespace_and_punctuation  = " \t\r\n\f;,=";

int main()

       // normal parsing of a string into a vector of strings
       std::string s("Somewhere down the road");
       std::vector<std::string> result;
       if( strtk::parse( s, whitespace, result ) )
       
           for(size_t i = 0; i < result.size(); ++i )
            std::cout << result[i] << std::endl;
       
    

      // parsing a string into a vector of floats with other separators
       // besides spaces

       std::string s("3.0, 3.14; 4.0");
       std::vector<float> values;
       if( strtk::parse( s, whitespace_and_punctuation, values ) )
       
           for(size_t i = 0; i < values.size(); ++i )
            std::cout << values[i] << std::endl;
       
    

      // parsing a string into specific variables

       std::string s("angle = 45; radius = 9.9");
       std::string w1, w2;
       float v1, v2;
       if( strtk::parse( s, whitespace_and_punctuation, w1, v1, w2, v2) )
       
           std::cout << "word " << w1 << ", value " << v1 << std::endl;
           std::cout << "word " << w2 << ", value " << v2 << std::endl;
       
    

    return 0;

【讨论】:

【参考方案26】:
/// split a string into multiple sub strings, based on a separator string
/// for example, if separator="::",
///
/// s = "abc" -> "abc"
///
/// s = "abc::def xy::st:" -> "abc", "def xy" and "st:",
///
/// s = "::abc::" -> "abc"
///
/// s = "::" -> NO sub strings found
///
/// s = "" -> NO sub strings found
///
/// then append the sub-strings to the end of the vector v.
/// 
/// the idea comes from the findUrls() function of "Accelerated C++", chapt7,
/// findurls.cpp
///
void split(const string& s, const string& sep, vector<string>& v)

    typedef string::const_iterator iter;
    iter b = s.begin(), e = s.end(), i;
    iter sep_b = sep.begin(), sep_e = sep.end();

    // search through s
    while (b != e)
        i = search(b, e, sep_b, sep_e);

        // no more separator found
        if (i == e)
            // it's not an empty string
            if (b != e)
                v.push_back(string(b, e));
            break;
        
        else if (i == b)
            // the separator is found and right at the beginning
            // in this case, we need to move on and search for the
            // next separator
            b = i + sep.length();
        
        else
            // found the separator
            v.push_back(string(b, i));
            b = i;
        
    

boost 库很好,但它们并不总是可用。用手做这类事情也是一种很好的大脑锻炼。这里我们只是使用了 STL 中的 std::search() 算法,见上面的代码。

【讨论】:

【参考方案27】:

我一直在寻找用任意长度的分隔符分割字符串的方法,所以我从头开始编写它,因为现有的解决方案不适合我。

这是我的小算法,只使用 STL:

//use like this
//std::vector<std::wstring> vec = Split<std::wstring> (L"Hello##world##!", L"##");

template <typename valueType>
static std::vector <valueType> Split (valueType text, const valueType& delimiter)

    std::vector <valueType> tokens;
    size_t pos = 0;
    valueType token;

    while ((pos = text.find(delimiter)) != valueType::npos) 
    
        token = text.substr(0, pos);
        tokens.push_back (token);
        text.erase(0, pos + delimiter.length());
    
    tokens.push_back (text);

    return tokens;

据我测试,它可以与任何长度和形式的分隔符一起使用。使用 string 或 wstring 类型实例化。

算法所做的只是搜索分隔符,获取到分隔符的字符串部分,删除分隔符并再次搜索,直到找不到为止。

希望对你有帮助。

【讨论】:

【参考方案28】:

对我来说似乎很奇怪,对于我们所有有速度意识的书呆子来说,没有人提出使用编译时生成的查找表作为分隔符的版本(示例实现进一步向下)。使用查找表和迭代器应该在效率上击败 std::regex,如果您不需要击败 regex,只需使用它,它是 C++11 的标准并且超级灵活。

有些人已经建议使用正则表达式,但对于新手来说,这里是一个打包的示例,应该完全符合 OP 的期望:

std::vector<std::string> split(std::string::const_iterator it, std::string::const_iterator end, std::regex e = std::regex"\\w+")
    std::smatch m;
    std::vector<std::string> ret;
    while (std::regex_search (it,end,m,e)) 
        ret.emplace_back(m.str());              
        std::advance(it, m.position() + m.length()); //next start position = match position + match length
    
    return ret;

std::vector<std::string> split(const std::string &s, std::regex e = std::regex"\\w+")  //comfort version calls flexible version
    return split(s.cbegin(), s.cend(), std::move(e));

int main ()

    std::string str "Some people, excluding those present, have been compile time constants - since puberty.";
    auto v = split(str);
    for(const auto&s:v)
        std::cout << s << std::endl;
    
    std::cout << "crazy version:" << std::endl;
    v = split(str, std::regex"[^e]+");  //using e as delim shows flexibility
    for(const auto&s:v)
        std::cout << s << std::endl;
    
    return 0;

如果我们需要更快并接受所有字符必须为 8 位的约束,我们可以在编译时使用元编程制作一个查找表:

template<bool...> struct BoolSequence;        //just here to hold bools
template<char...> struct CharSequence;        //just here to hold chars
template<typename T, char C> struct Contains;   //generic
template<char First, char... Cs, char Match>    //not first specialization
struct Contains<CharSequence<First, Cs...>,Match> :
    Contains<CharSequence<Cs...>, Match>;     //strip first and increase index
template<char First, char... Cs>                //is first specialization
struct Contains<CharSequence<First, Cs...>,First>: std::true_type ; 
template<char Match>                            //not found specialization
struct Contains<CharSequence<>,Match>: std::false_type;

template<int I, typename T, typename U> 
struct MakeSequence;                            //generic
template<int I, bool... Bs, typename U> 
struct MakeSequence<I,BoolSequence<Bs...>, U>:  //not last
    MakeSequence<I-1, BoolSequence<Contains<U,I-1>::value,Bs...>, U>;
template<bool... Bs, typename U> 
struct MakeSequence<0,BoolSequence<Bs...>,U>   //last  
    using Type = BoolSequence<Bs...>;
;
template<typename T> struct BoolASCIITable;
template<bool... Bs> struct BoolASCIITable<BoolSequence<Bs...>>
    /* could be made constexpr but not yet supported by MSVC */
    static bool isDelim(const char c)
        static const bool table[256] = Bs...;
        return table[static_cast<int>(c)];
       
;
using Delims = CharSequence<'.',',',' ',':','\n'>;  //list your custom delimiters here
using Table = BoolASCIITable<typename MakeSequence<256,BoolSequence<>,Delims>::Type>;

有了这一点,制作getNextToken 函数就很容易了:

template<typename T_It>
std::pair<T_It,T_It> getNextToken(T_It begin,T_It end)
    begin = std::find_if(begin,end,std::not1(Table)); //find first non delim or end
    auto second = std::find_if(begin,end,Table);      //find first delim or end
    return std::make_pair(begin,second);

使用也很简单:

int main() 
    std::string s"Some people, excluding those present, have been compile time constants - since puberty.";
    auto it = std::begin(s);
    auto end = std::end(s);
    while(it != std::end(s))
        auto token = getNextToken(it,end);
        std::cout << std::string(token.first,token.second) << std::endl;
        it = token.second;
    
    return 0;

这是一个活生生的例子:http://ideone.com/GKtkLQ

【讨论】:

是否可以使用字符串分隔符进行标记化? 此版本仅针对单字符分隔符进行了优化,使用查找表不适合多字符(字符串)分隔符,因此在效率上更难击败正则表达式。【参考方案29】:

使用regex_token_iterators 的解决方案:

#include <iostream>
#include <regex>
#include <string>

using namespace std;

int main()

    string str("The quick brown fox");

    regex reg("\\s+");

    sregex_token_iterator iter(str.begin(), str.end(), reg, -1);
    sregex_token_iterator end;

    vector<string> vec(iter, end);

    for (auto a : vec)
    
        cout << a << endl;
    

【讨论】:

这应该是排名靠前的答案。这是在 C++ >= 11 中执行此操作的正确方法。 我很高兴我一直滚动到这个答案(目前只有 9 个赞成票)。这正是执行此任务的 C++11 代码应有的样子! 不依赖外部库并使用已有库的优秀答案 很好的答案,在分隔符方面提供了最大的灵活性。一些注意事项:使用 \s+ 正则表达式可以避免文本中间出现空标记,但如果文本以空格开头,则会给出空的第一个标记。此外,正则表达式似乎很慢:在我的笔记本电脑上,对于 20 MB 的随机文本,它需要 0.6 秒,而 strtok、strsep 或 Parham 使用 str.find_first_of 的答案需要 0.014 秒,Perl 需要 0.027 秒,Python 需要 0.021 秒.对于短文本,速度可能不是问题。 好吧,也许它看起来很酷,但这显然是对正则表达式的过度使用。仅当您不关心性能时才合理。【参考方案30】:

我之前只使用标准库制作了词法分析器/标记器。代码如下:

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

using namespace std;

string seps(string& s) 
    if (!s.size()) return "";
    stringstream ss;
    ss << s[0];
    for (int i = 1; i < s.size(); i++) 
        ss << '|' << s[i];
    
    return ss.str();


void Tokenize(string& str, vector<string>& tokens, const string& delimiters = " ")

    seps(str);

    // Skip delimiters at beginning.
    string::size_type lastPos = str.find_first_not_of(delimiters, 0);
    // Find first "non-delimiter".
    string::size_type pos = str.find_first_of(delimiters, lastPos);

    while (string::npos != pos || string::npos != lastPos)
    
        // Found a token, add it to the vector.
        tokens.push_back(str.substr(lastPos, pos - lastPos));
        // Skip delimiters.  Note the "not_of"
        lastPos = str.find_first_not_of(delimiters, pos);
        // Find next "non-delimiter"
        pos = str.find_first_of(delimiters, lastPos);
    


int main(int argc, char *argv[])

    vector<string> t;
    string s = "Tokens for everyone!";

    Tokenize(s, t, "|");

    for (auto c : t)
        cout << c << endl;

    system("pause");

    return 0;

【讨论】:

以上是关于如何在 C++ 中标记字符串?的主要内容,如果未能解决你的问题,请参考以下文章

在 C++ 中查找和比较 Unicode 字符

C++ / 类编译和字符串属性:“在 '=' 标记之前需要 `)'

如何在 C++ 中搜索 std::string 中的子字符串?

C++分割字符串? [复制]

如何在 C++ 程序中的 2 个特定字符之间比较 2 个文件中的文本行

如何使用 C++ 获取硬件的唯一标识符 [关闭]