How to split a string in C++
来源:互联网 发布:csol2淘宝刷枪 编辑:程序博客网 时间:2024/05/17 02:31
这个问题是说, 怎么得到组成一句话的各个单词, 或者得到CSV中的各个数据片段.
这在C++中是个很简单的问题, 却有很多种答案.
有3种方案, 每种有利有弊. 使用时请自己选择最佳方案. 这篇文章的目的是说明 迭代器的接口是如何优胜于简单的容器的, 并且阐明 design of the STL 是何等强大.
方案1使用的标准组件(虽然方案1.2 做了微调). 方案2相对好点但使用了boost
. 而方案3 更好但使用了ranges
. 所以到底应该用哪个, 取决于你需要什么和你能使用什么.
Solution 1: Iterating on a stream
Stepping into the world of streams
“流” 是一个 能生成 与源或希望连接的目标 的联系 的对象. 流可以从源中获取信息(std::istream
), 或为目标提供信息(std::ostream
), 或者两者皆可(std::iostream
).
源和目标可以是标准输入(std::cin
), 标准输出(std::cout
), 一个文件, 或者一个字符串, 前提是方式得当.
对流的主要操作包括:
- 对于输入流: 使用操作符>>
从里面读取信息
- 对于输出流: 使用操作符<<
, 向它推入信息
一个指向字符串的输入流, std::istringstream
, 有个有趣的特性: 它的操作符>>
在源字符串中制造出去向下一个空格的字符串.
istream_iterator
std::istream_iterator
是连接输入流的迭代器.
它代表了输入迭代器的普遍接口, 但它的操作符++
更像是输入流.
istream_iterator
以它从流里读取的类型为模板. 我们现在使用istream_iterator<std::string>
, 它从流里读取字符串, 分离时为我们提供一个字符串.
当到达流的终点时, 流向它的迭代器发送信号, 然后迭代器被标记为结束.
Solution 1.1
现在, 我们可以借迭代器的接口使用算法, 这真切地证明了STL
设计的灵活性. 为了使用STL
, 我们需要一个begin
和一个end
(请参考Inserting several elements into an STL container efficiently). begin
是一个 还没开始着手分割的字符串的istreamstream
的迭代器: std::istream_iterator<std::string>(iss)
. 按照惯例, end
的默认值也是个istream_iterator
: std::istream_iterator<string>()
.
代码如下:
std::string text = "Let me split this into words";std::istringstream iss(text);std::vector<std::string> results((std::istream_iterator<std::string>(iss)), std::istream_iterator<std::string>());
第一个参数的额外的括号是为了避免与一个函数调用的歧义–请参考Scott Meyers的著作Effective STL 条目6 “most vexing parse”
优:
- 仅使用标准组件
- 除字符串外, 对所有流都适用
劣:
- 只能以空格为分隔符进行分割, 而且这在解析CSV时会是个至关重要的问题
- 在性能方面有待优化(但如果这不是影响你整个程序的瓶颈, 这也不是个大问题)
- 很多人认为仅为了分割一个字符串, 写了太多代码
Solution1.2: Pimp my operator>>
导致上面两条劣势的原因是同一个: istream_iterator
从流里读取字符串时调用的操作符>>
. 这个操作符做了很多事: 在下一个空格处停止(这是我们的最初的需求, 但这个不能自定义), 格式化, 读取然后设置一些标志位, 构造对象, 等等. 而以上这些, 大部分我们是不需要的.
所以我们希望自己实现下面的函数:
std::istream& operator>>(std::istream& is, std::string& output){ // ...does lots of things...}
实际上, 我们无法改变这些, 因为这是在标注库里的. 我们可以用另一个类型重载它, 但是这个类型需要是string
的一种.
所以现在的需求就是, 用另一种类型伪装成string
. 有两种方案: 继承std::string
和 用显式转换封装string
. 这里我们选择继承.
假如我们希望以逗号为分割符分割一个字符串:
class WordDelimitedByCommas: pulic std::string{};
我必须承认这是有争议的. 有人会说:”std::string
没有虚析构函数, 所以你不应该继承它!” 这可能, 大概, 也许是有一点点点点武断.
这里我要说的是, 继承本身不会产生问题. 诚然, 当一个指向WordDelimitedByCommas
的指针以std::string
的形式被delete
掉时, 会产生问题. 继续读, 你会发现, 我们不会这么做. 现在我们可以阻止写代码的人借WordDelimitedByCommas
突发冷箭破坏程序吗? 我们不能. 但是这个险值得我们冒吗? 请继续读, 然后你自己判断.
现在为了仅实现我们需要的功能, 我们可以重载操作符>>
: 获取下一个逗号之前的所有字符. 这个可以借用getline
函数实现:
std::istream& operator>>(std::istream* is, std::WordDelimitedByCommas&){ std::getline(is, output, ','); return is;}
返回值is
保证了可以连续调用操作符>>
现在我们可以写初级代码了:
std::string text = "Let,me,split,this,into,words";std::istringstream iss(text);std::vector<std::string> results((std::istream_iterator<WordDelimitedByCommas>(iss)), std::istream_iterator<WordDelimitedByCommas>());
我们可以通过模板化WordDelimitedByCommas
泛华所有的分隔符:
template<char delemiter>class WordDelimitedBy: pulic std::string{};
现在以分号举例:
std::string text = "Let;me;split;this;into;words";std::istringstream iss(text);std::vector<std::string> results((std::istream_iterator<WordDelimitedBy<';'>>(iss)), std::istream_iterator<WordDelimitedBy<';'>>());
优:
- 编译时允许任何分隔符
- 不仅是字符串, 对任何流都可以操作
- 比方案1更快(快20%到30%)
劣:
- 虽然可以很方便的复用, 但仍不是标准
- 仅仅为了分割一个字符串, 这个方案仍然使用了大量代码
Solution2: Using boost::split
这个方案比方案1高级, 除非你需要对所有的流都进行操作.
#include <boost/algorithm/string.hpp>std::string text = "Let me split this into words";std::vector<std::string> result;boost::split<results, text, [](char c){return ' ' == c;});
传给boost::split
的第三个参数是一个函数或函数对象, 确定一个字符是不是分隔符. 上面的例子是使用lambda
表达式, 传入一个char
, 返回这个char
是否是空格.
boost::split
的实现很简单: 在到达字符串的结束位置之前, 重复地调用find_if
.
优:
- 非常直观的接口
- 允许任何分隔符, 甚至是多个
- 高效: 比方案1.1 快 60%
劣:
- 暂不是标准: 需要用到boost
Solution 3(未来): Usingranges
虽然它们现在还没有像标准库甚至boost
里的组件一样被广泛使用, ranges
是future of the STL . 在未来几年, 会大量面世.
Eric Neiber 的 range-v3 库 提供了非常友好的接口.
为了生成一个字符串的分割view
, 代码如下:
std::string text = "Let me split this into words";auto splitText = text | view::split(' ');
它有很多有趣的特性, 诸如 使用一个子字符串作为分隔符. ranges
会被C++20
引入, 所以我们应该能在几年之内就可以使用这个功能了.
So, how do I split my string?
如果你能使用boost
, 务必使用方案2. 或者你可以自己写算法, 像boost
那样基于find_if
分割字符串.
如果你不想这么做, 你可以使用标准, 即方案1.1, 如果你需要自定义分隔符, 或者发现1.1是个瓶颈, 那么你可以选择方案1.2 .
如果你可以使用ranges
, 那么就应该选择方案3.
翻译原文: http://www.fluentcpp.com/2017/04/21/how-to-split-a-string-in-c/
- How to split a string to array in objective-c?
- How to split a string in C++
- How to split string in C++(分割字符串)
- Java – How to split a string
- Powershell - how to split string by a string
- split string in C
- How to convert from int to string in objective c
- How to convert from int to string in objective c
- How to Parse a string to an int? [C#]
- How to generate a random alpha-numeric string in java
- How to group anagrams in a string into an array
- How to remove the duplicated value in a String array
- How to set NULL to a pointer in Managed C++?
- How To Split column In SQL
- how to handle the c string and comments in flex
- How can I convert String to Int in C#?
- How to use epoll? A complete example in C
- How to use epoll? A complete example in C
- iOS CoreAnimation(三)CoreGraphics绘图,drawRect,CALayerDelegate,displayLayer
- 第8章 迭代1——基础
- Add to List 451. Sort Characters By Frequency
- js-函数
- 解决办法
- How to split a string in C++
- Zipper
- c++中关于线性表的实现,单链表
- jQuery的选择器中的通配符[id^='code']或[name^='code']及jquery选择器总结
- XSS攻击方式
- 一些实用的JavaGUI技巧
- Linux学习(三):删除命令rm的注意事项
- USACO-section 1.3 Mixing Milk[贪心]
- 创建恢复目录+创建恢复目录步骤