Java มีวิธีแบ่งสะดวก:
String str = "The quick brown fox";
String[] results = str.split(" ");
มีวิธีง่าย ๆ ใน C ++ หรือไม่
Java มีวิธีแบ่งสะดวก:
String str = "The quick brown fox";
String[] results = str.split(" ");
มีวิธีง่าย ๆ ใน C ++ หรือไม่
คำตอบ:
อัลกอริธึมไลบรารีมาตรฐาน C ++ นั้นค่อนข้างเป็นสากลโดยรอบตัววนซ้ำมากกว่าคอนเทนเนอร์คอนกรีต น่าเสียดายที่นี่ทำให้ยากที่จะให้split
ฟังก์ชั่นเหมือน Java ในไลบรารีมาตรฐาน C ++ แม้ว่าจะไม่มีใครโต้แย้งว่าสิ่งนี้จะสะดวก แต่ประเภทผลตอบแทนของมันจะเป็นอย่างไร std::vector<std::basic_string<…>>
? อาจ แต่จากนั้นเราถูกบังคับให้ทำการจัดสรร (อาจซ้ำซ้อนและมีค่าใช้จ่ายสูง)
แต่ C ++ เสนอวิธีมากมายในการแยกสตริงตามตัวคั่นที่ซับซ้อนโดยพลการ แต่ไม่มีสิ่งใดที่ถูกห่อหุ้มเหมือนในภาษาอื่น หลายวิธีกรอกบล็อกโพสต์ทั้งหมด
ที่ง่ายที่สุดที่คุณสามารถย้ำใช้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_iterator
sเนื้อหาของสตรีมสตริงสามารถคัดลอกไปยังเวกเตอร์โดยใช้ตัวสร้างช่วงตัววนซ้ำ
หลายไลบรารี (เช่นBoost.Tokenizer ) มีโทเค็นเฉพาะ
การแยกขั้นสูงเพิ่มเติมต้องการนิพจน์ทั่วไป C ++ จัดทำขึ้นstd::regex_token_iterator
เพื่อจุดประสงค์นี้โดยเฉพาะ:
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{}
);
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;
}
}
char_separator
สร้าง ( drop_empty_tokens
เป็นค่าเริ่มต้นทางเลือกคือkeep_empty_tokens
)
.h
สำหรับส่วนหัว C)
นี่คือตัวอย่างง่ายๆที่แท้จริง:
#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;
}
ใช้ strtok ในความคิดของฉันไม่จำเป็นต้องสร้างคลาสที่มีโทเค็นอยู่นอกเสียจากว่า strtok จะไม่ให้สิ่งที่คุณต้องการ มันอาจจะไม่ แต่ใน 15 ปีของการเขียนรหัสการแยกวิเคราะห์ใน C และ C ++ ฉันเคยใช้ strtok นี่คือตัวอย่าง
char myString[] = "The quick brown fox";
char *p = strtok(myString, " ");
while (p) {
printf ("Token: %s\n", p);
p = strtok(NULL, " ");
}
คำเตือนเล็กน้อย (ซึ่งอาจไม่เหมาะกับความต้องการของคุณ) สายอักขระถูก "ทำลาย" ในกระบวนการซึ่งหมายความว่าอักขระ EOS ถูกวางไว้ในจุดที่ตัวคั่น การใช้งานที่ถูกต้องอาจทำให้คุณต้องสร้างสตริงที่ไม่ใช่เวอร์ชัน นอกจากนี้คุณยังสามารถเปลี่ยนรายการของตัวคั่นกลางแยกวิเคราะห์
ในความเห็นของฉันโค้ดข้างต้นนั้นง่ายกว่าและใช้งานง่ายกว่าการเขียนคลาสแยกต่างหาก สำหรับฉันนี่เป็นหนึ่งในฟังก์ชั่นที่ภาษาให้และมันทำได้ดีและเรียบร้อย มันเป็นเพียงแค่โซลูชัน "C based" มันเหมาะสมมันง่ายและคุณไม่จำเป็นต้องเขียนโค้ดพิเศษจำนวนมาก :-)
getline
อีกวิธีที่รวดเร็วคือการใช้งาน สิ่งที่ต้องการ:
stringstream ss("bla bla");
string s;
while (getline(ss, s, ' ')) {
cout << s << endl;
}
หากคุณต้องการคุณสามารถสร้างsplit()
วิธีการคืนค่า a vector<string>
ซึ่งเป็นประโยชน์จริงๆ
คุณสามารถใช้สตรีมตัววนซ้ำและอัลกอริทึมการคัดลอกเพื่อทำสิ่งนี้โดยตรง
#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
วิธีนี้ฉันรู้ว่าวัตถุของฉันมาจากไหนนั่นเป็นเพียงเรื่องของสไตล์
ไม่มีคนที่กระทำผิดกฎหมาย แต่สำหรับปัญหาดังกล่าวที่เรียบง่ายที่คุณกำลังทำสิ่งที่วิธีที่ซับซ้อนเกินไป มีจำนวนมากของเหตุผลที่จะใช้เป็น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());
}
}
ตัวอย่าง (สำหรับกรณีของดั๊ก)
#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] );
}
และใช่เรามีทางแยก () คืนเวกเตอร์ใหม่แทนที่จะผ่านอันหนึ่งมันไม่สำคัญที่จะห่อหุ้มและเกินพิกัด แต่ขึ้นอยู่กับสิ่งที่ฉันทำฉันมักพบว่าการนำวัตถุที่มีอยู่แล้วกลับมาใช้ใหม่ได้ดีกว่าการสร้างวัตถุใหม่เสมอ (ตราบใดที่ฉันไม่ลืมที่จะล้างเวกเตอร์ในระหว่าง!)
อ้างอิง: http://www.cplusplus.com/reference/string/string/
(ตอนแรกฉันเขียนคำตอบของคำถามของ Doug: C ++ Strings Modifying and Extracting โดยแยก (ปิด)แต่เนื่องจาก Martin York ปิดคำถามนั้นโดยมีตัวชี้อยู่ตรงนี้ ... ฉันจะสรุปรหัสของฉัน)
std::string
ชั้นไม่รวมฟังก์ชั่น split ()?
start = ((end > (theString.size() - theDelimiter.size())) ? string::npos : end + theDelimiter.size());
while (start != string::npos)
นอกจากนี้ฉันจะตรวจสอบสตริงย่อยเพื่อให้แน่ใจว่าไม่ว่างเปล่าก่อนที่จะแทรกลงในเวกเตอร์
วิธีแก้ปัญหาโดยใช้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มีฟังก์ชั่นแยกเข้มแข็งเพิ่ม :: อัลกอริทึม :: แยก
โปรแกรมตัวอย่าง:
#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"
""
ฉันรู้ว่าคุณขอวิธีแก้ปัญหา C ++ แต่คุณอาจพิจารณาว่ามีประโยชน์:
Qt
#include <QString>
...
QString str = "The quick brown fox";
QStringList results = str.split(" ");
ข้อได้เปรียบเหนือ Boost ในตัวอย่างนี้คือมันเป็นการแมปแบบหนึ่งต่อหนึ่งกับรหัสโพสต์ของคุณโดยตรง
ดูเพิ่มเติมได้ที่เอกสารประกอบของ Qt
นี่คือคลาส 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;
}
ตัวอย่าง:
std::vector <std::string> v;
Tokenizer s("split this string", " ");
while (s.NextToken())
{
v.push_back(s.GetToken());
}
นี่เป็นโซลูชัน 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);
}
}
ลองสด !
pystringเป็นไลบรารี่ขนาดเล็กที่ใช้ฟังก์ชั่นสตริงของไพ ธ อนรวมถึงวิธีการแบ่ง:
#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 ++ String Toolkit ห้องสมุด
นี่คือตัวอย่างของวิธีการใช้งานที่ฉันโพสต์ไว้ที่อื่นใน 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;
}
ตรวจสอบตัวอย่างนี้ มันอาจช่วยคุณ ..
#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 มีโทเค็นที่ดีมาก จาก 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
หากคุณยินดีที่จะใช้ C คุณสามารถใช้ฟังก์ชั่นstrtok คุณควรใส่ใจกับปัญหาหลายเธรดเมื่อใช้งาน
สำหรับสิ่งที่เรียบง่ายฉันเพิ่งใช้สิ่งต่อไปนี้:
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, กล้อง) ฉันไม่เคยใช้ฟังก์ชั่นนี้สำหรับสิ่งที่ซับซ้อนหรือเวลาสำคัญกว่าการอ่านไฟล์กำหนดค่าภายนอกเมื่อเริ่มต้น
คุณสามารถใช้ไลบรารีนิพจน์ทั่วไปและแก้ปัญหาโดยใช้นิพจน์ทั่วไป
ใช้ expression (\ w +) และตัวแปรใน \ 1 (หรือ $ 1 ขึ้นอยู่กับการใช้ไลบรารี่ของนิพจน์ทั่วไป)
คำแนะนำที่ซับซ้อนมากเกินไปที่นี่ ลอง 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;
}
ฉันคิดว่านั่นเป็นสิ่งที่>>
ผู้ปฏิบัติงานในสายสตรีมใช้เพื่อ:
string word; sin >> word;
คำตอบอดัมเพียร์ซยังมีมือหมุน tokenizer const char*
การใน เป็นบิตปัญหามากขึ้นจะทำอย่างไรกับ iterators เพราะการเพิ่มstring
สิ้น 's iterator จะไม่ได้กำหนด ที่กล่าวว่าเนื่องจาก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;
}
หากคุณกำลังมองหาความซับซ้อนเชิงนามธรรมโดยใช้ฟังก์ชั่นมาตรฐานเนื่องจากOn Freund แนะนำ strtok
เป็นตัวเลือกที่ง่าย:
vector<string> tokens;
for (auto i = strtok(data(str), " "); i != nullptr; i = strtok(nullptr, " ")) tokens.push_back(i);
หากคุณไม่มีสิทธิ์เข้าถึง C ++ 17 คุณจะต้องแทนที่data(str)
ดังเช่นในตัวอย่างนี้: http://ideone.com/8kAGoa
แม้ว่าจะไม่ได้แสดงในตัวอย่าง แต่strtok
ไม่จำเป็นต้องใช้ตัวคั่นเดียวกันสำหรับแต่ละโทเค็น ด้วยความได้เปรียบนี้แม้ว่าจะมีข้อเสียหลายประการ:
strtok
ไม่สามารถใช้กับหลาย ๆ อย่างstrings
ในเวลาเดียวกัน: a nullptr
ต้องถูกส่งผ่านเพื่อดำเนินการโทเค็นปัจจุบันstring
หรือchar*
tokenize ใหม่ที่ต้องส่งต่อ (มีบางการใช้งานที่ไม่ได้มาตรฐานซึ่งสนับสนุนสิ่งนี้อย่างไรก็ตาม:strtok_s
)strtok
นี้ไม่สามารถใช้กับหลาย ๆ เธรดพร้อมกันได้ (ซึ่งอาจเป็นการนำไปใช้งานที่กำหนดไว้ตัวอย่างเช่น: การใช้งานของ Visual Studio คือเธรดที่ปลอดภัย )strtok
แก้ไขstring
มันจะทำงานบนดังนั้นจึงไม่สามารถใช้กับconst string
s, const char*
s หรือสตริงตัวอักษรเพื่อโทเค็นใด ๆ เหล่านี้ด้วยstrtok
หรือเพื่อดำเนินการกับstring
ผู้ที่ต้องรักษาเนื้อหาของstr
จะต้องคัดลอกแล้วคัดลอกได้ จะดำเนินการในC ++ 20ทำให้เรามีsplit_view
tokenize สตริงในลักษณะที่ไม่ทำลาย: https://topanswers.xyz/cplusplus?q=749#a874
วิธีการก่อนหน้านี้ไม่สามารถสร้าง tokenized ในสถานที่ที่มีความหมายโดยไม่ต้องสรุปให้พวกเขาในการทำงานช่วยเหลือพวกเขาไม่สามารถเตรียม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>() };
การสร้างที่จำเป็นistringstream
สำหรับตัวเลือกนี้มีค่าใช้จ่ายสูงกว่า 2 ตัวเลือกก่อนหน้านี้อย่างไรก็ตามโดยทั่วไปค่าใช้จ่ายนี้จะถูกซ่อนอยู่ในค่าใช้จ่ายในการstring
จัดสรร
หากไม่มีตัวเลือกข้างต้นเป็น flexable เพียงพอสำหรับความต้องการของคุณ tokenization ตัวเลือกที่ยืดหยุ่นมากที่สุดคือการใช้regex_token_iterator
ของหลักสูตรมีความยืดหยุ่นนี้มาค่าใช้จ่ายมากขึ้นอีกครั้ง แต่ครั้งนี้มีแนวโน้มที่จะถูกซ่อนอยู่ในstring
ค่าใช้จ่ายในการจัดสรร พูดเช่นเราต้องการทำเครื่องหมายตามเครื่องหมายจุลภาคที่ไม่ใช้ Escape รวมถึงการกิน white-space ด้วยการป้อนข้อมูลต่อไปนี้: 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() };
strtok_s
เป็นมาตรฐาน C11 โดยวิธีการ strtok_r
เป็นมาตรฐาน POSIX2001 ระหว่างทั้งสองนั้นจะมีเวอร์ชัน re-entrant มาตรฐานstrtok
สำหรับแพลตฟอร์มส่วนใหญ่
#include <cstring>
จะมีเฉพาะรุ่นc99strtok
เท่านั้น ดังนั้นสมมติฐานของฉันคือคุณเพียงแค่แสดงความคิดเห็นนี้เป็นวัสดุสนับสนุนแสดงให้เห็นถึงการใช้งานที่เฉพาะเจาะจงของstrtok
ส่วนขยาย?
strtok_s
ให้บริการโดยทั้ง C11 และเป็นส่วนขยายแบบสแตนด์อโลนใน C runtime ของ Microsoft มีประวัติที่น่าสนใจเล็กน้อยที่นี่ซึ่งการ_s
ทำงานของ Microsoft กลายเป็นมาตรฐาน C
ฉันรู้ว่าคำถามนี้ตอบแล้ว แต่ฉันต้องการมีส่วนร่วม อาจจะแก้ปัญหาของฉันเป็นเรื่องง่าย แต่นี่คือสิ่งที่ฉันมาด้วย:
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;
}
โปรดแสดงความคิดเห็นหากมีวิธีการที่ดีกว่าในบางสิ่งบางอย่างในรหัสของฉันหรือหากมีสิ่งผิดปกติ
UPDATE:เพิ่มตัวคั่นทั่วไป
ต่อไปนี้เป็นวิธีการที่ช่วยให้คุณควบคุมว่ามีโทเค็นว่างอยู่หรือไม่ (เช่น 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;
}
ดูเหมือนว่าแปลกสำหรับฉันที่พวกเราทุกคนเร่งความเร็วให้กับผู้สนใจที่นี่ดังนั้นไม่มีใครแสดงรุ่นที่ใช้เวลารวบรวมที่สร้างขึ้นค้นหาตารางสำหรับตัวคั่น (ตัวอย่างการนำไปใช้เพิ่มเติม) การใช้ lookup table และตัววนซ้ำควรใช้ std :: regex อย่างมีประสิทธิภาพหากคุณไม่จำเป็นต้องเอาชนะ regex ให้ใช้มันมาตรฐานของ C ++ 11 และความยืดหยุ่นสูงเป็นพิเศษ
บางคนได้แนะนำ regex แล้ว แต่สำหรับ noobs ที่นี่เป็นตัวอย่างของแพ็คเกจที่ควรทำตามที่ 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 บิตเราสามารถสร้างตารางค้นหาในเวลาคอมไพล์โดยใช้ metaprogramming:
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
คุณสามารถใช้ประโยชน์จากการเพิ่ม :: 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;
}
ต่อไปนี้คือ Swiss-Army Knife-tokenizers ของฉันสำหรับการแยกสตริงด้วยช่องว่างการบัญชีสำหรับสตริงเดี่ยวและคู่ที่มีเครื่องหมายคำพูดรวมถึงการลอกอักขระจากผลลัพธ์ ผมใช้ 4.x RegexBuddy ในการสร้างมากที่สุดของรหัสข้อมูลโค้ด แต่ฉันเพิ่มที่กำหนดเองจัดการลอกคำพูดและสิ่งอื่น ๆ น้อย
#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;
}
หากทราบความยาวสูงสุดของสตริงป้อนข้อมูลที่จะโทเค็นหนึ่งสามารถใช้ประโยชน์จากนี้และใช้รุ่นที่รวดเร็วมาก ฉันกำลังร่างแนวคิดพื้นฐานด้านล่างซึ่งได้รับแรงบันดาลใจจากทั้ง strtok () และ "ส่วนต่อท้าย" โครงสร้างข้อมูลอธิบายของ Jon Bentley "Programming Perls" รุ่นที่ 2 บทที่ 15 คลาส C ++ ในกรณีนี้ให้องค์กรและความสะดวกสบายบางอย่างเท่านั้น การใช้งาน การใช้งานที่แสดงสามารถขยายได้อย่างง่ายดายสำหรับการลบอักขระช่องว่างนำหน้าและต่อท้ายในโทเค็น
โดยทั่วไปจะสามารถแทนที่อักขระตัวคั่นด้วยการสิ้นสุดสตริง '\ 0'-characters และตั้งค่าพอยน์เตอร์เป็นโทเค็นที่มีสตริงที่ปรับเปลี่ยน ในกรณีที่รุนแรงเมื่อสตริงประกอบด้วยตัวคั่นเท่านั้นหนึ่งตัวจะได้รับความยาวของสตริงบวก 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
boost::tokenizer
เป็นเพื่อนของคุณ แต่ให้พิจารณาทำให้โค้ดของคุณพกพาได้โดยอ้างอิงกับปัญหาสากล (i18n) โดยใช้wstring
/ wchar_t
แทนที่จะเป็นแบบดั้งเดิมstring
/ char
ประเภท
#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
เป็นการนำไปปฏิบัติที่น่ากลัวซึ่งไม่มีใครควรใช้เว้นแต่จำเป็นจริงๆ
รหัส 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;
}