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

Bil*_*ard 400 c++ string split tokenize

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

String str = "The quick brown fox";
String[] results = str.split(" ");
Run Code Online (Sandbox Code Playgroud)

有没有一种简单的方法在C++中执行此操作?

Fer*_*cio 187

升压标记生成器类可以使这种相当简单的事情:

#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;
    }
}
Run Code Online (Sandbox Code Playgroud)

针对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;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @puk - 这是C++头文件的常用后缀.(比如C头的`.h`) (5认同)
  • 要小心`char_separator`构造函数的第三个参数(`drop_empty_tokens`是默认值,替代是`keep_empty_tokens`). (3认同)

Ada*_*rce 168

这是一个非常简单的:

#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;
}
Run Code Online (Sandbox Code Playgroud)

  • 这不是"最佳"答案,因为它仍然使用字符串文字,它是普通的C常量字符数组.我相信提问者问他是否可以标记后者引入的"字符串"类型的C++字符串. (5认同)

Kon*_*lph 140

您可以使用该std::string::find方法轻松构建简单的案例.但是,看看Boost.Tokenizer.这很棒.Boost通常有一些非常酷的字符串工具.

  • 可悲的是,并非所有项目都能提供增强功能.我将不得不寻找一个非提升的答案. (52认同)
  • 并非每个项目都对"开源"开放.我在受到严格监管的行业工作.真的,这不是问题.这只是生活中的一个事实.Boost并非随处可用. (34认同)
  • @NonlinearIdeas另一个问题/答案根本不是关于开源项目.*any*项目也是如此.也就是说,我当然理解MISRA C这样的限制标准,但是你知道你无论如何都要从头开始构建所有东西(除非你碰巧找到一个兼容的库 - 很少见).无论如何,重点不在于"Boost不可用" - 而是您有特殊要求,几乎*任何*通用答案都不适合. (4认同)
  • @Dmitry什么是"STL barf"?!并且整个社区都非常赞成更换C预处理器 - 事实上,有提议要做到这一点.但是你建议使用PHP或其他语言反而是向后迈出了一大步. (3认同)

Mar*_*ark 113

使用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, " ");
}
Run Code Online (Sandbox Code Playgroud)

一些警告(可能不适合您的需要).字符串在此过程中被"销毁",这意味着EOS字符内嵌在分隔符中.正确使用可能需要您创建字符串的非const版本.您还可以在解析时更改分隔符列表.

在我看来,上面的代码比为它编写一个单独的类要简单得多,也更容易使用.对我来说,这是该语言提供的那些功能之一,并且它干净利落地完成了它.它只是一个"基于C"的解决方案.这很合适,很简单,而且你不必编写很多额外的代码:-)

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

use*_*978 99

另一种快速方法是使用getline.就像是:

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

while (getline(ss, s, ' ')) {
 cout << s << endl;
}
Run Code Online (Sandbox Code Playgroud)

如果你愿意,你可以做一个简单的split()方法返回一个vector<string>,这是非常有用的.

  • 这很好但是必须记住,通过这样做,不考虑默认分隔符'\n'.这个例子可行,但是如果你使用的是:while(getline(inFile,word,''))其中inFile是包含多行的ifstream对象,你将得到funnny结果.. (4认同)
  • 我在使用这种技术时遇到问题,字符串中的0x0A字符使得while循环过早退出.否则,这是一个简单而快速的解决方案. (2认同)

Kei*_*thB 82

您可以使用流,迭代器和复制算法直接执行此操作.

#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);
}
Run Code Online (Sandbox Code Playgroud)

  • @Vadi:因为编辑其他人的帖子非常具有侵入性.@pheze:我更喜欢让`std`这样我知道我的对象来自哪里,这只是一种风格问题. (79认同)
  • 尽管"std ::"前缀令人恼火或丑陋,但最好将它们包含在示例代码中,以便完全清楚这些函数的来源.如果他们打扰你,那么在你窃取这个例子并将其声称为你自己之后用"使用"替换它们是微不足道的. (59认同)
  • 是的!他说的话!最佳实践是使用std前缀.毫无疑问,任何大型代码库都会拥有自己的库和命名空间,并且当您开始导致命名空间冲突时,使用"using namespace std"会让您头疼. (19认同)
  • 我发现那些std ::恼人的阅读..为什么不使用"使用"? (17认同)
  • 我理解你的理由,我认为如果它适合你,它实际上是一个很好的选择,但从教学的角度来看,我实际上同意pheze.更容易阅读和理解一个完全外来的例子,例如在顶部使用"using namespace std",因为它需要较少的工作来解释以下行...特别是在这种情况下,因为一切都来自标准库.您可以通过一系列"使用std :: string;"来轻松阅读和显示对象的来源.特别是因为功能太短了. (7认同)
  • 用户 - 因为有人可能会复制此示例,我们不希望他们使用'使用':) (6认同)
  • @pheze:先生,为什么不编辑而不是抱怨? (2认同)

Mr.*_*Ree 46

没有进攻的乡亲,但对于这样一个简单的问题,你在做事情的方式太复杂了.使用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());
    }
}
Run Code Online (Sandbox Code Playgroud)

例如(对于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] );
}
Run Code Online (Sandbox Code Playgroud)

是的,我们可以让split()返回一个新的向量,而不是传入一个.包装和重载是微不足道的.但是根据我正在做的事情,我经常发现重新使用预先存在的对象而不是总是创建新对象会更好.(只要我不忘记在中间清空矢量!)

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

(我最初写的回答Doug的问题:基于分隔符的C++字符串修改和提取(已关闭).但是由于Martin York用指针关闭了这个问题......我只是概括了我的代码.)

  • 为什么要定义一个只在一个地方使用的宏.你的UASSERT如何比标准断言更好.将比较拆分成3个令牌,除了需要更多的逗号之外别无其他需要. (11认同)
  • 呃,为什么`std :: string`类不包含split()函数? (10认同)

w.b*_*w.b 41

使用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;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这应该是排名靠前的答案.这是在C++> = 11中执行此操作的正确方法. (5认同)
  • 好吧,也许它看起来很酷,但这显然是正则表达式的过度使用。仅当您不关心性能时才是合理的。 (4认同)

Raz*_*Raz 34

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;
}
Run Code Online (Sandbox Code Playgroud)

输出:

"a"
"b"
" c "
""
"e"
"f"
""
Run Code Online (Sandbox Code Playgroud)


siv*_*udh 25

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

Qt的

#include <QString>

...

QString str = "The quick brown fox"; 
QStringList results = str.split(" "); 
Run Code Online (Sandbox Code Playgroud)

在这个例子中,优于Boost的优势在于它是一个一对一的映射到你的帖子的代码.

Qt文档中查看更多内容


vzc*_*czc 22

这是一个示例tokenizer类,可以执行您想要的操作

//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;
}
Run Code Online (Sandbox Code Playgroud)

例:

std::vector <std::string> v;
Tokenizer s("split this string", " ");
while (s.NextToken())
{
    v.push_back(s.GetToken());
}
Run Code Online (Sandbox Code Playgroud)


Par*_*ham 18

这是一个简单的仅限STL的解决方案(约5行!)std::find,std::find_first_not_of它使用并处理分隔符的重复(例如空格或句点),以及前导和尾随分隔符:

#include <string>
#include <vector>

void tokenize(std::string str, std::vector<string> &token_v){
    size_t start = str.find_first_not_of(DELIMITER), end=start;

    while (start != std::string::npos){
        // Find next occurence of delimiter
        end = str.find(DELIMITER, start);
        // Push back the token found into vector
        token_v.push_back(str.substr(start, end-start));
        // Skip all occurences of the delimiter to find new start
        start = str.find_first_not_of(DELIMITER, end);
    }
}
Run Code Online (Sandbox Code Playgroud)

试试吧直播!

  • 这是一个很好的但我认为你需要使用find_first_of()而不是find()才能使多个分隔符正常工作. (3认同)
  • 使用find_first_not_of查找起始位置时,将跳过@ user755921多个分隔符. (2认同)

dbr*_*dbr 16

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, "-");
Run Code Online (Sandbox Code Playgroud)

  • 哇,你已经回答了*我的*直接问题和许多未来的问题。我知道 c++ 很强大。但是当拆分字符串导致源代码如上述答案时,这显然令人沮丧。我很想知道像这样的其他图书馆可以降低更高级别的语言便利。 (3认同)

ein*_*ica 12

如果您使用 C++ 范围 - 完整的range-v3库,而不是 C++20 接受的有限功能 - 您可以这样做:

auto results = str | ranges::views::tokenize(" ",1);
Run Code Online (Sandbox Code Playgroud)

...这是懒惰评估的。您也可以将向量设置为此范围:

auto results = str | ranges::views::tokenize(" ",1) | ranges::to<std::vector>();
Run Code Online (Sandbox Code Playgroud)

str如果有 n 个字符组成 m 个单词,则需要 O(m) 空间和 O(n) 时间。

另请参阅库自己的标记化示例,此处


Dan*_*nyK 10

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

这是一个如何使用它的例子,我已经发布了stackoverflow上的其他地方.

#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;
}
Run Code Online (Sandbox Code Playgroud)


小智 8

检查此示例.它可能会帮助你..

#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;
}
Run Code Online (Sandbox Code Playgroud)


Jon*_*Mee 7

Adam Pierce 的回答提供了一个手工标记的分词器,将const char*. 使用迭代器有点问题,因为递增 astring的结束迭代器是 undefined。也就是说,鉴于string str{ "The quick brown fox" }我们当然可以做到这一点:

auto start = find(cbegin(str), cend(str), ' ');
vector<string> tokens{ string(cbegin(str), start) };

while (start != cend(str)) {
    const auto finish = find(++start, cend(str), ' ');

    tokens.push_back(string(start, finish));
    start = finish;
}
Run Code Online (Sandbox Code Playgroud)

Live Example


如果您希望通过使用标准功能来抽象复杂性,正如On Freund 建议的那样,这 strtok是一个简单的选择:

vector<string> tokens;

for (auto i = strtok(data(str), " "); i != nullptr; i = strtok(nullptr, " ")) tokens.push_back(i);
Run Code Online (Sandbox Code Playgroud)

如果您无权访问 C++17,则需要data(str)像本示例中那样替换:http : //ideone.com/8kAGoa

尽管示例中没有演示,但strtok不必为每个标记使用相同的分隔符。除了这个优势,还有几个缺点:

  1. strtok不能在多个可使用strings在同一时间:无论是nullptr必须传递给继续标记化的当前string或新的char*来标记必须被传递(也有其做然而支持这一点,如一些非标准的实现:strtok_s
  2. 出于同样的原因strtok,不能同时在多个线程上使用(但这可能是实现定义的,例如:Visual Studio 的实现是线程安全的
  3. 调用strtok修改了string它正在操作的对象,因此它不能用于const strings、const char*s 或文字字符串,以标记其中的任何一个strtok或对string需要保留的内容进行操作,str必须被复制,然后副本可以被操作

为我们提供了split_view以非破坏性方式标记字符串的方法:https ://topanswers.xyz/cplusplus?q=749#a874


前面的方法不能vector就地生成标记化,这意味着如果不将它们抽象为辅助函数,它们就无法初始化const vector<string> tokens。该功能接受任何空白分隔符的能力可以使用istream_iterator. 例如给出:const string str{ "The quick \tbrown \nfox" }我们可以这样做:

istringstream is{ str };
const vector<string> tokens{ istream_iterator<string>(is), istream_iterator<string>() };
Run Code Online (Sandbox Code Playgroud)

Live Example

istringstream此选项所需的构造成本远高于前 2 个选项,但此成本通常隐藏在string分配费用中。


如果上述选项都不能满足您的标记化需求,那么最灵活的选项regex_token_iterator当然是使用具有这种灵活性的更大费用,但这同样可能隐藏在string分配成本中。例如,假设我们想要基于非转义逗号进行标记,也吃空格,给定以下输入:const string str{ "The ,qu\\,ick ,\tbrown, fox" }我们可以这样做:

const regex re{ "\\s*((?:[^\\\\,]|\\\\.)*?)\\s*(?:,|$)" };
const vector<string> tokens{ sregex_token_iterator(cbegin(str), cend(str), re, 1), sregex_token_iterator() };
Run Code Online (Sandbox Code Playgroud)

Live Example


Jim*_*xas 6

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
Run Code Online (Sandbox Code Playgroud)