Java có một phương thức phân chia thuận tiện:
String str = "The quick brown fox";
String[] results = str.split(" ");
Có một cách dễ dàng để làm điều này trong C ++?
Java có một phương thức phân chia thuận tiện:
String str = "The quick brown fox";
String[] results = str.split(" ");
Có một cách dễ dàng để làm điều này trong C ++?
Câu trả lời:
Các thuật toán thư viện chuẩn C ++ khá phổ biến dựa trên các trình vòng lặp hơn là các thùng chứa cụ thể. Thật không may, điều này làm cho việc cung cấp một split
hàm giống Java trong thư viện chuẩn C ++ trở nên khó khăn , mặc dù không ai tranh luận rằng việc này sẽ thuận tiện. Nhưng loại trở lại của nó sẽ là gì?std::vector<std::basic_string<…>>
? Có thể, nhưng sau đó chúng tôi buộc phải thực hiện phân bổ (có khả năng dự phòng và tốn kém).
Thay vào đó, C ++ cung cấp rất nhiều cách để phân chia chuỗi dựa trên các dấu phân cách phức tạp tùy ý, nhưng không có cách nào trong số chúng được gói gọn như trong các ngôn ngữ khác. Nhiều cách điền vào toàn bộ bài viết trên blog .
Đơn giản nhất, bạn có thể lặp lại bằng cách sử dụng std::string::find
cho đến khi bạn nhấn std::string::npos
và trích xuất nội dung bằng cách sử dụngstd::string::substr
.
Một phiên bản linh hoạt hơn (và thành ngữ, nhưng cơ bản) để phân tách trên khoảng trắng sẽ sử dụng std::istringstream
:
auto iss = std::istringstream{"The quick brown fox"};
auto str = std::string{};
while (iss >> str) {
process(str);
}
Sử dụng std::istream_iterator
s , nội dung của luồng chuỗi cũng có thể được sao chép vào một vectơ bằng cách sử dụng hàm tạo phạm vi lặp của nó.
Nhiều thư viện (như Boost.Tokenizer ) cung cấp mã thông báo cụ thể.
Chia tách nâng cao hơn đòi hỏi các biểu thức thông thường. C ++ cung cấp cụ thể std::regex_token_iterator
cho mục đích này:
auto const str = "The quick brown fox"s;
auto const re = std::regex{R"(\s+)"};
auto const vec = std::vector<std::string>(
std::sregex_token_iterator{begin(str), end(str), re, -1},
std::sregex_token_iterator{}
);
Lớp Boost tokenizer có thể làm cho loại điều này khá đơn giản:
#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ập nhật cho 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;
}
}
char_separator
tạo ( drop_empty_tokens
là mặc định, thay thế là keep_empty_tokens
).
.h
đối với tiêu đề C)
Đây là một đơn giản thực sự:
#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;
}
Sử dụng strtok. Theo tôi, không cần phải xây dựng một lớp xung quanh token hóa trừ khi strtok không cung cấp cho bạn những gì bạn cần. Có thể không, nhưng trong hơn 15 năm viết các mã phân tích cú pháp khác nhau trong C và C ++, tôi đã luôn sử dụng strtok. Đây là một ví dụ
char myString[] = "The quick brown fox";
char *p = strtok(myString, " ");
while (p) {
printf ("Token: %s\n", p);
p = strtok(NULL, " ");
}
Một vài cảnh báo (có thể không phù hợp với nhu cầu của bạn). Chuỗi bị "hủy" trong quy trình, nghĩa là các ký tự của EOS được đặt nội tuyến trong các điểm phân định. Việc sử dụng đúng có thể yêu cầu bạn tạo một phiên bản không phải là chuỗi. Bạn cũng có thể thay đổi danh sách các dấu phân cách giữa parse.
Theo ý kiến riêng của tôi, đoạn mã trên đơn giản và dễ sử dụng hơn nhiều so với việc viết một lớp riêng cho nó. Đối với tôi, đây là một trong những chức năng mà ngôn ngữ cung cấp và nó thực hiện tốt và sạch sẽ. Nó chỉ đơn giản là một giải pháp "dựa trên C". Điều đó phù hợp, thật dễ dàng và bạn không phải viết thêm nhiều mã :-)
Một cách nhanh chóng khác là sử dụng getline
. Cái gì đó như:
stringstream ss("bla bla");
string s;
while (getline(ss, s, ' ')) {
cout << s << endl;
}
Nếu bạn muốn, bạn có thể thực hiện một split()
phương thức đơn giản trả về một vector<string>
, điều này thực sự hữu ích.
Bạn có thể sử dụng các luồng, các trình vòng lặp và thuật toán sao chép để thực hiện điều này khá trực tiếp.
#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
theo cách này tôi biết đối tượng của mình đến từ đâu, đó chỉ là vấn đề về phong cách.
Không folks hành vi phạm tội, nhưng đối với một vấn đề đơn giản như vậy, bạn đang làm cho mọi thứ cách quá phức tạp. Có rất nhiều lý do để sử dụng Boost . Nhưng đối với một cái gì đó đơn giản, nó giống như đánh một con ruồi với một tạ 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());
}
}
Ví dụ: (đối với trường hợp của 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] );
}
Và vâng, chúng ta có thể đã chia () trả về một vectơ mới thay vì truyền vào một. Nó không quan trọng để bọc và quá tải. Nhưng tùy thuộc vào những gì tôi đang làm, tôi thường thấy tốt hơn khi sử dụng lại các đối tượng đã tồn tại thay vì luôn tạo ra những đối tượng mới. (Chỉ cần tôi không quên làm trống vectơ ở giữa!)
Tham khảo: http://www.cplusplus.com/reference/opes/opes/ .
(Ban đầu tôi đã viết một câu trả lời cho câu hỏi của Doug: Sửa đổi và trích xuất chuỗi C ++ dựa trên Dấu phân cách (đã đóng) . Nhưng vì Martin York đã đóng câu hỏi đó bằng một con trỏ ở đây ... Tôi sẽ chỉ khái quát mã của mình.)
std::string
lớp không bao gồm hàm split ()?
start = ((end > (theString.size() - theDelimiter.size())) ? string::npos : end + theDelimiter.size());
và vòng lặp while nên là while (start != string::npos)
. Ngoài ra, tôi kiểm tra chuỗi con để chắc chắn rằng nó không trống trước khi chèn nó vào vector.
Một giải pháp sử dụng regex_token_iterator
s:
#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;
}
}
Boost có chức năng phân chia mạnh: boost :: Thuật toán :: split .
Chương trình mẫu:
#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;
}
Đầu ra:
"a"
"b"
" c "
""
"e"
"f"
""
Tôi biết bạn đã yêu cầu một giải pháp C ++, nhưng bạn có thể xem xét điều này hữu ích:
Qt
#include <QString>
...
QString str = "The quick brown fox";
QStringList results = str.split(" ");
Ưu điểm so với Boost trong ví dụ này là nó ánh xạ trực tiếp từ một đến một vào mã bài đăng của bạn.
Xem thêm tại tài liệu Qt
Đây là một lớp mã thông báo mẫu có thể làm những gì bạn muốn
//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;
}
Thí dụ:
std::vector <std::string> v;
Tokenizer s("split this string", " ");
while (s.NextToken())
{
v.push_back(s.GetToken());
}
Đây là một giải pháp đơn giản chỉ STL (~ 5 dòng!) Sử dụng std::find
và std::find_first_not_of
xử lý các lần lặp lại của dấu phân cách (ví dụ như khoảng trắng hoặc dấu chấm), cũng như các dấu phân cách hàng đầu và dấu:
#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);
}
}
Dùng thử trực tiếp !
pystring là một thư viện nhỏ thực hiện một loạt các hàm chuỗi của Python, bao gồm cả phương thức phân tách:
#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, "-");
Tôi đăng câu trả lời này cho câu hỏi tương tự.
Đừng phát minh lại bánh xe. Tôi đã sử dụng một số thư viện và nhanh nhất và linh hoạt nhất mà tôi đã gặp là: Thư viện bộ công cụ chuỗi C ++ .
Dưới đây là một ví dụ về cách sử dụng nó mà tôi đã đăng ở nơi khác trên 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;
}
Kiểm tra ví dụ này. Nó có thể giúp bạn ..
#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 >> tmps ) { std::cout << tmps << "\n"; }
MFC / ATL có mã thông báo rất đẹp. Từ 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
Nếu bạn sẵn sàng sử dụng C, bạn có thể sử dụng hàm strtok . Bạn nên chú ý đến các vấn đề đa luồng khi sử dụng nó.
Đối với những thứ đơn giản tôi chỉ sử dụng như sau:
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;
}
Từ chối trách nhiệm hèn nhát: Tôi viết phần mềm xử lý dữ liệu thời gian thực trong đó dữ liệu đến thông qua các tệp nhị phân, ổ cắm hoặc một số lệnh gọi API (thẻ I / O, máy ảnh). Tôi không bao giờ sử dụng chức năng này cho một cái gì đó phức tạp hoặc quan trọng về thời gian hơn là đọc các tệp cấu hình bên ngoài khi khởi động.
Bạn có thể chỉ cần sử dụng một thư viện biểu thức chính quy và giải quyết điều đó bằng cách sử dụng các biểu thức thông thường.
Sử dụng biểu thức (\ w +) và biến trong \ 1 (hoặc $ 1 tùy thuộc vào thư viện triển khai biểu thức chính quy).
Nhiều đề xuất quá phức tạp ở đây. Hãy thử giải pháp std :: chuỗi đơn giản này:
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;
}
Tôi nghĩ rằng đó là những gì >>
toán tử trên các chuỗi chuỗi là:
string word; sin >> word;
Câu trả lời của Adam Pierce cung cấp một mã thông báo quay tay lấy trong một const char*
. Sẽ khó khăn hơn một chút khi thực hiện với các trình vòng lặp vì việc tăng string
vòng lặp kết thúc của một vòng lặp không được xác định . Điều đó nói rằng, cho string str{ "The quick brown fox" }
chúng ta chắc chắn có thể thực hiện điều này:
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;
}
Nếu bạn đang tìm kiếm sự phức tạp trừu tượng bằng cách sử dụng chức năng tiêu chuẩn, như On Freund gợi ý strtok
là một tùy chọn đơn giản:
vector<string> tokens;
for (auto i = strtok(data(str), " "); i != nullptr; i = strtok(nullptr, " ")) tokens.push_back(i);
Nếu bạn không có quyền truy cập vào C ++ 17, bạn sẽ cần phải thay thế data(str)
như trong ví dụ này: http://ideone.com/8kAGoa
Mặc dù không được thể hiện trong ví dụ, strtok
không cần sử dụng cùng một dấu phân cách cho mỗi mã thông báo. Cùng với lợi thế này, có một số nhược điểm:
strtok
không thể được sử dụng trên nhiều lần strings
cùng một lúc: nullptr
Phải thông qua để tiếp tục mã hóa dòng điện string
hoặc char*
mã thông báo mới phải được thông qua (có một số triển khai không chuẩn hỗ trợ điều này, tuy nhiên, chẳng hạn như strtok_s
:)strtok
không thể được sử dụng trên nhiều luồng đồng thời (tuy nhiên điều này có thể được xác định là triển khai, ví dụ: Triển khai của Visual Studio là luồng an toàn )strtok
sẽ sửa đổi string
nó đang hoạt động, do đó, nó không thể được sử dụng trên const string
các const char*
chuỗi s, s hoặc bằng chữ, để mã hóa bất kỳ trong số này với strtok
hoặc để hoạt động trên một string
nội dung của ai đó cần được bảo tồn str
, sau đó phải sao chép được vận hành trênc ++ 20cung cấp cho chúng tôi split_view
mã thông báo chuỗi, theo cách không phá hủy: https://topanswers.xyz/cplusplus?q=749#a874
Các phương thức trước đó không thể tạo ra một mã thông báo vector
tại chỗ, nghĩa là không trừu tượng hóa chúng thành một hàm trợ giúp mà chúng không thể khởi tạo const vector<string> tokens
. Chức năng đó và khả năng chấp nhận bất kỳ dấu phân cách không gian trắng nào có thể được khai thác bằng cách sử dụng istream_iterator
. Ví dụ đã cho: const string str{ "The quick \tbrown \nfox" }
chúng ta có thể làm điều này:
istringstream is{ str };
const vector<string> tokens{ istream_iterator<string>(is), istream_iterator<string>() };
Việc xây dựng một yêu cầu istringstream
cho tùy chọn này có chi phí lớn hơn nhiều so với 2 tùy chọn trước đó, tuy nhiên chi phí này thường được ẩn trong chi phí string
phân bổ.
Nếu không có tùy chọn nào ở trên đủ linh hoạt cho nhu cầu mã thông báo của bạn, thì tùy chọn linh hoạt nhất là sử dụng một regex_token_iterator
khóa học có tính linh hoạt này sẽ có chi phí lớn hơn, nhưng một lần nữa điều này có thể ẩn trong string
chi phí phân bổ. Ví dụ: chúng tôi muốn mã hóa dựa trên dấu phẩy không thoát, cũng ăn khoảng trắng, với đầu vào sau: const string str{ "The ,qu\\,ick ,\tbrown, fox" }
chúng tôi có thể làm điều này:
const regex re{ "\\s*((?:[^\\\\,]|\\\\.)*?)\\s*(?:,|$)" };
const vector<string> tokens{ sregex_token_iterator(cbegin(str), cend(str), re, 1), sregex_token_iterator() };
strtok_s
Nhân tiện, là tiêu chuẩn C11. strtok_r
là tiêu chuẩn POSIX2001. Giữa cả hai, có một phiên bản tái đăng ký tiêu chuẩn strtok
cho hầu hết các nền tảng.
#include <cstring>
chỉ bao gồm phiên bản c99 của strtok
. Vì vậy, giả định của tôi là bạn chỉ cung cấp nhận xét này làm tài liệu hỗ trợ, thể hiện sự sẵn có cụ thể của các strtok
tiện ích mở rộng?
strtok_s
được cung cấp bởi cả C11 và như một phần mở rộng độc lập trong thời gian chạy C của Microsoft. Có một chút tò mò về lịch sử ở đây, nơi các _s
chức năng của Microsoft đã trở thành tiêu chuẩn C.
Tôi biết câu hỏi này đã được trả lời nhưng tôi muốn đóng góp. Có thể giải pháp của tôi hơi đơn giản nhưng đây là những gì tôi nghĩ ra:
vector<string> get_words(string const& text, string const& separator)
{
vector<string> result;
string tmp = text;
size_t first_pos = 0;
size_t second_pos = tmp.find(separator);
while (second_pos != string::npos)
{
if (first_pos != second_pos)
{
string word = tmp.substr(first_pos, second_pos - first_pos);
result.push_back(word);
}
tmp = tmp.substr(second_pos + separator.length());
second_pos = tmp.find(separator);
}
result.push_back(tmp);
return result;
}
Hãy bình luận nếu có một cách tiếp cận tốt hơn cho một cái gì đó trong mã của tôi hoặc nếu có gì đó không đúng.
CẬP NHẬT: thêm dấu phân cách chung
Đây là một cách tiếp cận cho phép bạn kiểm soát xem các mã thông báo trống được bao gồm (như strsep) hay bị loại trừ (như 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;
}
Có vẻ kỳ lạ với tôi rằng với tất cả chúng ta, những người mọt sách có ý thức về tốc độ ở đây trên SO, không ai đưa ra một phiên bản sử dụng bảng tra cứu thời gian biên dịch được tạo cho dấu phân cách (ví dụ triển khai xuống). Sử dụng bảng tra cứu và các trình vòng lặp sẽ đánh bại std :: regex về hiệu quả, nếu bạn không cần phải đánh bại regex, chỉ cần sử dụng nó, tiêu chuẩn của nó là C ++ 11 và siêu linh hoạt.
Một số người đã đề xuất regex rồi nhưng đối với các noobs ở đây là một ví dụ được đóng gói sẽ thực hiện chính xác những gì OP mong đợi:
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;
}
Nếu chúng ta cần phải nhanh hơn và chấp nhận ràng buộc rằng tất cả các ký tự phải là 8 bit, chúng ta có thể tạo bảng tra cứu tại thời gian biên dịch bằng cách sử dụng siêu lập trình:
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>;
Với điều đó, việc tạo ra một getNextToken
chức năng rất dễ dàng:
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);
}
Sử dụng nó cũng dễ dàng:
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;
}
Dưới đây là một ví dụ trực tiếp: http://ideone.com/GKtkLQ
bạn có thể tận dụng boost :: make_find_iterator. Một cái gì đó tương tự như thế này:
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;
}
Đây là Bộ dao mã thông báo Swiss® Army Knife của tôi để phân tách chuỗi theo khoảng trắng, chiếm các chuỗi được gói và trích dẫn kép cũng như tước các ký tự đó khỏi kết quả. Tôi đã sử dụng RegexBuddy 4.x để tạo hầu hết đoạn mã, nhưng tôi đã thêm xử lý tùy chỉnh để tước trích dẫn và một vài thứ khác.
#include <string>
#include <locale>
#include <regex>
std::vector<std::wstring> tokenize_string(std::wstring string_to_tokenize) {
std::vector<std::wstring> tokens;
std::wregex re(LR"(("[^"]*"|'[^']*'|[^"' ]+))", std::regex_constants::collate);
std::wsregex_iterator next( string_to_tokenize.begin(),
string_to_tokenize.end(),
re,
std::regex_constants::match_not_null );
std::wsregex_iterator end;
const wchar_t single_quote = L'\'';
const wchar_t double_quote = L'\"';
while ( next != end ) {
std::wsmatch match = *next;
const std::wstring token = match.str( 0 );
next++;
if (token.length() > 2 && (token.front() == double_quote || token.front() == single_quote))
tokens.emplace_back( std::wstring(token.begin()+1, token.begin()+token.length()-1) );
else
tokens.emplace_back(token);
}
return tokens;
}
Nếu độ dài tối đa của chuỗi đầu vào được mã hóa được biết đến, người ta có thể khai thác điều này và thực hiện một phiên bản rất nhanh. Tôi đang phác thảo ý tưởng cơ bản dưới đây, được lấy cảm hứng từ cả strtok () và cấu trúc -data "hậu tố" mô tả phiên bản 2 "Lập trình Perls" của Jon Bentley, chương 15. Lớp C ++ trong trường hợp này chỉ mang lại cho tổ chức và sự thuận tiện sử dụng. Việc triển khai được hiển thị có thể dễ dàng được mở rộng để xóa các ký tự khoảng trắng hàng đầu và dấu trong các mã thông báo.
Về cơ bản, người ta có thể thay thế các ký tự dấu phân cách bằng các ký tự '\ 0' chấm dứt chuỗi và đặt các con trỏ thành các mã thông báo với chuỗi đã sửa đổi. Trong trường hợp cực đoan khi chuỗi chỉ bao gồm các dấu phân cách, một chuỗi có độ dài chuỗi cộng với 1 kết quả là các mã thông báo trống. Đó là thực tế để nhân đôi chuỗi được sửa đổi.
Tập tin tiêu đề:
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;
}
};
Tệp triển khai:
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++ ] );
}
Một kịch bản sử dụng sẽ là:
// 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 ) );
}
đầu ra:
Item1
Item2
Item3
boost::tokenizer
là bạn của bạn, nhưng hãy xem xét việc làm cho mã của bạn có thể di động với tham chiếu đến các vấn đề quốc tế hóa (i18n) bằng cách sử dụng wstring
/ wchar_t
thay vì các di sản string
/ char
loại.
#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;
}
wchar_t
là một loại phụ thuộc thực hiện khủng khiếp mà không ai nên sử dụng trừ khi thực sự cần thiết.
Mã C ++ đơn giản (tiêu chuẩn C ++ 98), chấp nhận nhiều dấu phân cách (được chỉ định trong chuỗi std ::), chỉ sử dụng vectơ, chuỗi và trình lặp.
#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;
}