ฉันจะทำซ้ำคำของสตริงได้อย่างไร?


2986

ฉันพยายามย้ำคำของสตริง

สตริงสามารถสันนิษฐานได้ว่าประกอบด้วยคำที่คั่นด้วยช่องว่าง

โปรดทราบว่าฉันไม่สนใจฟังก์ชั่นสตริง C หรือการจัดการ / เข้าถึงตัวละครแบบนั้น นอกจากนี้โปรดให้ความสำคัญกับความเหนือกว่าประสิทธิภาพในคำตอบของคุณ

ทางออกที่ดีที่สุดที่ฉันมีตอนนี้คือ:

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

using namespace std;

int main()
{
    string s = "Somewhere down the road";
    istringstream iss(s);

    do
    {
        string subs;
        iss >> subs;
        cout << "Substring: " << subs << endl;
    } while (iss);
}

มีวิธีที่สง่างามกว่านี้หรือไม่?


617
เพื่อน ... ความสง่างามเป็นเพียงวิธีแฟนซีในการพูดว่า "ประสิทธิภาพ - ดู - สวย - สวย" ในหนังสือของฉัน อย่าอายที่จะไม่ใช้ฟังก์ชั่น C และวิธีการอย่างรวดเร็วเพื่อทำสิ่งใดเพียงเพราะมันไม่มีอยู่ในแม่แบบ;)

14
while (iss) { string subs; iss >> subs; cout << "Substring: " << sub << endl; }
pyon

21
@Eduardo: นั่นก็ผิดเช่นกัน ... คุณต้องทดสอบ iss ระหว่างพยายามที่จะสตรีมค่าอื่นและใช้ค่านั้นstring sub; while (iss >> sub) cout << "Substring: " << sub << '\n';
Tony Delroy

9
ตัวเลือกต่าง ๆ ใน C ++ เพื่อทำสิ่งนี้โดยค่าเริ่มต้น: cplusplus.com/faq/sequences/strings/split
hB0

14
มีความงดงามมากกว่าประสิทธิภาพที่น่าดึงดูด คุณลักษณะที่สง่างามรวมถึงจำนวนบรรทัดต่ำและความชัดเจนสูง IMHO Elegance ไม่ได้เป็นตัวแทนของประสิทธิภาพ แต่ยังคงไว้ซึ่งการบำรุงรักษา
Matt

คำตอบ:


1369

สำหรับสิ่งที่คุ้มค่าต่อไปนี้เป็นอีกวิธีในการแยกโทเค็นออกจากสตริงอินพุตโดยอาศัยสิ่งอำนวยความสะดวกไลบรารีมาตรฐานเท่านั้น มันเป็นตัวอย่างของพลังและความสง่างามที่อยู่เบื้องหลังการออกแบบของ STL

#include <iostream>
#include <string>
#include <sstream>
#include <algorithm>
#include <iterator>

int main() {
    using namespace std;
    string sentence = "And I feel fine...";
    istringstream iss(sentence);
    copy(istream_iterator<string>(iss),
         istream_iterator<string>(),
         ostream_iterator<string>(cout, "\n"));
}

แทนที่จะคัดลอกโทเค็นที่แยกไปยังเอาต์พุตสตรีมสามารถแทรกลงในคอนเทนเนอร์ได้โดยใช้copyอัลกอริทึมทั่วไปเดียวกัน

vector<string> tokens;
copy(istream_iterator<string>(iss),
     istream_iterator<string>(),
     back_inserter(tokens));

... หรือสร้างvectorโดยตรง:

vector<string> tokens{istream_iterator<string>{iss},
                      istream_iterator<string>{}};

164
เป็นไปได้หรือไม่ที่จะระบุตัวคั่นสำหรับสิ่งนี้ เช่นการแยกเครื่องหมายจุลภาคหรือไม่
l3dx

15
@ โจนาธาน: \ n ไม่ใช่ตัวคั่นในกรณีนี้มันเป็นตัวกำหนดให้ส่งออกไปยัง cout
huy

772
นี่เป็นวิธีการแก้ปัญหาที่ไม่ดีเนื่องจากไม่ใช้ตัวคั่นอื่นดังนั้นจึงไม่สามารถปรับขนาดได้และไม่สามารถบำรุงรักษาได้
HelloWorld

37
จริงๆแล้วสิ่งนี้สามารถทำงานได้ดีกับตัวคั่นอื่น ๆ (แม้ว่าการทำบางอย่างจะค่อนข้างน่าเกลียด) คุณสร้าง ctype facet ที่จัดประเภทตัวคั่นที่ต้องการเป็น whitespace สร้างโลแคลที่มี facet นั้นจากนั้น imbue stringstream กับ locale นั้นก่อนที่จะแยกสตริง
Jerry Coffin

53
@Kinderchocolate "สตริงสามารถสันนิษฐานได้ว่าประกอบด้วยคำที่คั่นด้วยช่องว่าง" - อืมฟังดูไม่ดีสำหรับปัญหาของคำถาม "ไม่สามารถปรับขนาดได้และไม่สามารถบำรุงรักษาได้" - ฮะเป็นคนดี
Christian Rau

2426

ฉันใช้สิ่งนี้เพื่อแยกสตริงโดยตัวคั่น อันแรกวางผลลัพธ์ลงในเวกเตอร์ที่สร้างไว้ล่วงหน้าส่วนที่สองส่งคืนเวกเตอร์ใหม่

#include <string>
#include <sstream>
#include <vector>
#include <iterator>

template <typename Out>
void split(const std::string &s, char delim, Out result) {
    std::istringstream iss(s);
    std::string item;
    while (std::getline(iss, item, delim)) {
        *result++ = item;
    }
}

std::vector<std::string> split(const std::string &s, char delim) {
    std::vector<std::string> elems;
    split(s, delim, std::back_inserter(elems));
    return elems;
}

โปรดทราบว่าวิธีนี้จะไม่ข้ามโทเค็นที่ว่างเปล่าดังนั้นรายการต่อไปนี้จะพบ 4 รายการซึ่งหนึ่งในนั้นว่างเปล่า:

std::vector<std::string> x = split("one:two::three", ':');

86
เพื่อหลีกเลี่ยงการข้ามโทเค็นที่ว่างเปล่าให้empty()ตรวจสอบ:if (!item.empty()) elems.push_back(item)
0x499602D2

11
วิธีการเกี่ยวกับ delim มีสองตัวอักษรเป็น->?
herohuyongtao

7
@herohuyongtao โซลูชันนี้ใช้ได้กับตัวคั่นถ่านเดี่ยวเท่านั้น
Evan Teran

4
@JeshwanthKumarNK ไม่จำเป็น แต่มันช่วยให้คุณทำสิ่งต่าง ๆ เช่นส่งผลลัพธ์โดยตรงไปยังฟังก์ชันเช่นนี้: f(split(s, d, v))ในขณะที่ยังคงได้รับประโยชน์จากการจัดสรรล่วงหน้าvectorหากคุณต้องการ
Evan Teran

8
Caveat: split ("one: two :: three", ':') และ split ("one: two :: three:", ':') คืนค่าเดิม
dshin

834

ทางออกที่เป็นไปได้โดยใช้ Boost อาจเป็น:

#include <boost/algorithm/string.hpp>
std::vector<std::string> strs;
boost::split(strs, "string to split", boost::is_any_of("\t "));

วิธีนี้อาจเร็วกว่าstringstreamวิธีนี้ และเนื่องจากเป็นฟังก์ชันแม่แบบทั่วไปจึงสามารถใช้เพื่อแยกสตริงประเภทอื่น ๆ (wchar ฯลฯ หรือ UTF-8) โดยใช้ตัวคั่นทุกชนิด

ดูเอกสารประกอบสำหรับรายละเอียด


35
ความเร็วไม่เกี่ยวข้องที่นี่เนื่องจากทั้งสองกรณีนี้ช้ากว่าฟังก์ชั่น strtok มาก
Tom

45
และสำหรับผู้ที่ยังไม่มีบูสเตอร์ ... สำเนา bcp มากกว่า 1,000 ไฟล์สำหรับสิ่งนี้ :)
โรมัน Starkov

12
คำเตือนเมื่อได้รับสตริงว่าง ("") วิธีนี้จะส่งคืนเวกเตอร์ที่มีสตริง "" ดังนั้นให้เพิ่ม "if (! string_to_split.empty ())" ก่อนการแยก
Offirmo

29
@Ian ผู้พัฒนาสมองกลฝังตัวไม่ได้ใช้การเพิ่ม
ACK_stoverflow

31
ในฐานะที่เป็นภาคผนวก: ฉันใช้บูสต์เมื่อฉันต้องเท่านั้นโดยปกติฉันชอบที่จะเพิ่มไลบรารีโค้ดของตัวเองซึ่งเป็นแบบสแตนด์อโลนและพกพาเพื่อที่ฉันจะได้รับโค้ดเฉพาะขนาดเล็กที่แม่นยำซึ่งบรรลุเป้าหมายที่กำหนด วิธีนี้ทำให้รหัสนั้นเป็นแบบไม่เปิดเผยต่อสาธารณชน, นักแสดง, เรื่องไร้สาระและพกพา Boost มีสถานที่ แต่ฉันขอแนะนำว่ามันเกินความจริงสำหรับสายโทเค็น: คุณคงไม่มีบ้านทั้งหลังของคุณถูกส่งไปยัง บริษัท วิศวกรรมเพื่อรับเล็บใหม่ตอกเข้ากับผนังเพื่อแขวนรูปภาพ .... พวกเขาอาจทำมันได้ ดีมาก แต่ข้อดีที่ไกลเกินกว่าข้อเสีย
GMasucci

362
#include <vector>
#include <string>
#include <sstream>

int main()
{
    std::string str("Split me by whitespaces");
    std::string buf;                 // Have a buffer string
    std::stringstream ss(str);       // Insert the string into a stream

    std::vector<std::string> tokens; // Create vector to hold our words

    while (ss >> buf)
        tokens.push_back(buf);

    return 0;
}

12
นอกจากนี้คุณยังสามารถแยกตัวคั่นอื่น ๆ ถ้าคุณใช้getlineในเช่นสภาพการแยกด้วยเครื่องหมายจุลภาคใช้while while(getline(ss, buff, ','))
Ali

181

สำหรับผู้ที่ไม่ดีพอที่จะเสียสละประสิทธิภาพทั้งหมดสำหรับขนาดโค้ดและดูว่า "ประสิทธิภาพ" เป็นประเภทของความสง่างามสิ่งต่อไปนี้น่าจะเป็นจุดที่น่าสนใจ

template < class ContainerT >
void tokenize(const std::string& str, ContainerT& tokens,
              const std::string& delimiters = " ", bool trimEmpty = false)
{
   std::string::size_type pos, lastPos = 0, length = str.length();

   using value_type = typename ContainerT::value_type;
   using size_type  = typename ContainerT::size_type;

   while(lastPos < length + 1)
   {
      pos = str.find_first_of(delimiters, lastPos);
      if(pos == std::string::npos)
      {
         pos = length;
      }

      if(pos != lastPos || !trimEmpty)
         tokens.push_back(value_type(str.data()+lastPos,
               (size_type)pos-lastPos ));

      lastPos = pos + 1;
   }
}

ฉันมักจะเลือกใช้std::vector<std::string>ประเภทเป็นพารามิเตอร์ที่สองของฉัน ( ContainerT) ... แต่list<>เร็วกว่าvector<>เมื่อไม่ต้องการเข้าถึงโดยตรงและคุณสามารถสร้างคลาสสตริงของคุณเองและใช้บางสิ่งบางอย่างstd::list<subString>ที่subStringไม่ทำสำเนาใด ๆ เพื่อความเร็วที่เหลือเชื่อ เพิ่มขึ้น

มันเร็วกว่าเป็นสองเท่าเร็วกว่าโทเค็นที่เร็วที่สุดในหน้านี้และเร็วกว่าคนอื่น ๆ เกือบ 5 เท่า ด้วยประเภทพารามิเตอร์ที่สมบูรณ์แบบคุณสามารถกำจัดการคัดลอกสตริงและรายการทั้งหมดเพื่อเพิ่มความเร็วเพิ่มเติม

นอกจากนี้จะไม่ทำการส่งคืนผลลัพธ์ (ไม่มีประสิทธิภาพอย่างยิ่ง) แต่จะส่งสัญญาณโทเค็นเป็นการอ้างอิงดังนั้นจึงอนุญาตให้คุณสร้างโทเค็นโดยใช้การโทรหลายสายหากคุณต้องการ

สุดท้ายช่วยให้คุณระบุว่าจะตัดโทเค็นที่ว่างเปล่าจากผลลัพธ์ผ่านพารามิเตอร์ทางเลือกล่าสุดหรือไม่

สิ่งที่ต้องการคือstd::string... ส่วนที่เหลือเป็นทางเลือก มันไม่ได้ใช้สตรีมหรือห้องสมุดเพิ่ม แต่มีความยืดหยุ่นเพียงพอที่จะยอมรับสิ่งแปลกปลอมเหล่านี้ได้ตามธรรมชาติ


5
ฉันเป็นแฟนตัวยงของเรื่องนี้ แต่สำหรับ g ++ (และอาจเป็นแนวปฏิบัติที่ดี) ทุกคนที่ใช้สิ่งนี้จะต้องการ typedefs และ typenames: typedef ContainerT Base; typedef typename Base::value_type ValueType; typedef typename ValueType::size_type SizeType; จากนั้นให้แทนที่ value_type และ size_types ตามลำดับ
aws

11
สำหรับพวกเราที่มีเทมเพลตและความคิดเห็นแรกเป็นของต่างชาติอย่างสมบูรณ์ตัวอย่างการใช้งาน cmplete ที่จำเป็นต้องมีจะน่ารัก
Wes Miller

3
อ่าฉันคิดออก ฉันใส่สาย C ++ จากความคิดเห็น aws 'ภายในร่างกายฟังก์ชั่นของ tokenize () จากนั้นแก้ไขบรรทัด tokens.push_back () เพื่อเปลี่ยน ContainerT :: value_type เป็นเพียง ValueType และเปลี่ยน (ContainerT :: value_type :: size_type) เป็น ( SizeType) แก้ไขบิต g ++ ที่ส่งเสียงครวญคราง เพียงแค่เรียกมันเป็นโทเค็น (some_string, some_vector);
Wes Miller

2
นอกเหนือจากการรันการทดสอบประสิทธิภาพสองสามอย่างกับข้อมูลตัวอย่างส่วนใหญ่ฉันได้ลดคำแนะนำให้น้อยที่สุดเท่าที่จะเป็นไปได้และยังเปิดใช้งานการคัดลอกหน่วยความจำน้อยที่สุดเท่าที่จะทำได้โดยใช้คลาสย่อยที่อ้างอิงเฉพาะ (ฉันรีดเอง แต่มีการใช้งานอื่น ๆ ) น่าเสียดายที่ไม่มีอะไรมากเกินไปที่เราสามารถทำได้เพื่อปรับปรุงในเรื่องนี้ แต่การเพิ่มขึ้นที่เพิ่มขึ้นนั้นเป็นไปได้
Marius

3
นั่นเป็นผลลัพธ์ที่ถูกต้องเมื่อtrimEmpty = trueใด โปรดจำไว้ว่า"abo"ไม่ใช่ตัวคั่นในคำตอบนี้ แต่รายการของอักขระตัวคั่น มันง่ายที่จะแก้ไขเพื่อใช้สตริงตัวคั่นเดียว (ฉันคิดว่าstr.find_first_ofควรเปลี่ยนเป็นstr.find_firstแต่ฉันอาจผิดได้ ... ไม่สามารถทดสอบได้)
Marius

158

นี่คือทางออกอื่น มันกะทัดรัดและมีประสิทธิภาพพอสมควร:

std::vector<std::string> split(const std::string &text, char sep) {
  std::vector<std::string> tokens;
  std::size_t start = 0, end = 0;
  while ((end = text.find(sep, start)) != std::string::npos) {
    tokens.push_back(text.substr(start, end - start));
    start = end + 1;
  }
  tokens.push_back(text.substr(start));
  return tokens;
}

มันสามารถ templatised ง่ายต่อการจัดการตัวคั่นสตริงสตริงกว้าง ฯลฯ

โปรดทราบว่าการแยก""ผลลัพธ์ในสตริงว่างเดียวและการแยก","(เช่น sep) ส่งผลให้มีสองสตริงว่าง

นอกจากนี้ยังสามารถขยายได้อย่างง่ายดายเพื่อข้ามโทเค็นที่ว่างเปล่า:

std::vector<std::string> split(const std::string &text, char sep) {
    std::vector<std::string> tokens;
    std::size_t start = 0, end = 0;
    while ((end = text.find(sep, start)) != std::string::npos) {
        if (end != start) {
          tokens.push_back(text.substr(start, end - start));
        }
        start = end + 1;
    }
    if (end != start) {
       tokens.push_back(text.substr(start));
    }
    return tokens;
}

หากต้องการแยกสตริงที่ตัวคั่นหลายตัวในขณะที่ต้องการข้ามโทเค็นที่ว่างเปล่าอาจใช้เวอร์ชันนี้:

std::vector<std::string> split(const std::string& text, const std::string& delims)
{
    std::vector<std::string> tokens;
    std::size_t start = text.find_first_not_of(delims), end = 0;

    while((end = text.find_first_of(delims, start)) != std::string::npos)
    {
        tokens.push_back(text.substr(start, end - start));
        start = text.find_first_not_of(delims, end);
    }
    if(start != std::string::npos)
        tokens.push_back(text.substr(start));

    return tokens;
}

10
รุ่นแรกนั้นเรียบง่ายและทำงานได้อย่างสมบูรณ์ การเปลี่ยนแปลงเพียงอย่างเดียวที่ฉันทำคือการส่งคืนผลลัพธ์โดยตรงแทนที่จะส่งผ่านเป็นพารามิเตอร์
gregschlom

2
เอาต์พุตถูกส่งเป็นพารามิเตอร์เพื่อประสิทธิภาพ หากผลลัพธ์ถูกส่งคืนจะต้องใช้สำเนาของเวกเตอร์หรือการจัดสรรฮีปซึ่งจะต้องทำให้เป็นอิสระ
Alec Thomas

2
ภาคผนวกเล็กน้อยในความคิดเห็นของฉันด้านบน: ฟังก์ชันนี้สามารถคืนค่าเวกเตอร์โดยไม่มีการลงโทษหากใช้ซีแมนทิกส์การย้าย C ++ 11
อเล็กซ์โทมัส

7
@AlecThomas: แม้กระทั่งก่อน C ++ 11 คอมไพเลอร์ส่วนใหญ่จะไม่เพิ่มประสิทธิภาพสำเนาที่ส่งคืนผ่าน NRVO หรือไม่ (+1 อย่างไรก็ตาม; รวบรัดมาก)
Marcelo Cantos

11
จากคำตอบทั้งหมดนี่เป็นหนึ่งในสิ่งที่ดึงดูดและยืดหยุ่นที่สุด ร่วมกับ getline พร้อมตัวคั่นแม้ว่าจะเป็นโซลูชันที่เห็นได้ชัดน้อยกว่า มาตรฐาน c ++ 11 ไม่มีอะไรสำหรับสิ่งนี้หรือไม่? c ++ 11 รองรับการชกมวยการ์ดในปัจจุบันหรือไม่?
Spacen Jasset

123

นี่เป็นวิธีที่ฉันชอบในการวนซ้ำผ่านสตริง คุณสามารถทำสิ่งที่คุณต้องการต่อคำ

string line = "a line of text to iterate through";
string word;

istringstream iss(line, istringstream::in);

while( iss >> word )     
{
    // Do something on `word` here...
}

เป็นไปได้ไหมที่จะประกาศwordว่าเป็นchar?
abatishchev

ขออภัย abatishchev, C ++ ไม่ใช่จุดแข็งของฉัน แต่ฉันคิดว่าคงไม่ยากที่จะเพิ่มการวนซ้ำภายในเพื่อวนรอบตัวละครทุกตัวในแต่ละคำ แต่ตอนนี้ฉันเชื่อว่าลูปปัจจุบันขึ้นอยู่กับช่องว่างสำหรับการแยกคำ จนกว่าคุณจะรู้ว่ามีเพียงตัวเดียวระหว่างทุกพื้นที่ซึ่งในกรณีนี้คุณก็สามารถโยน "คำว่า" เพื่อถ่าน ... ขอโทษฉันลาดเทจะมีการช่วยเหลือเพิ่มเติม ive รับหมายที่จะแปรงขึ้นกับฉัน C ++
gnomed

11
หากคุณประกาศคำว่าเป็นถ่านคำนั้นจะวนซ้ำอักขระที่ไม่ใช่ช่องว่างทั้งหมด มันง่ายพอที่จะลอง:stringstream ss("Hello World, this is*@#&$(@ a string"); char c; while(ss >> c) cout << c;
Wayne Werner

79

คล้ายกับคำถาม Stack Overflow ฉันจะโทเค็นสตริงใน C ++ ได้อย่างไร .

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

using namespace std;
using namespace boost;

int main(int argc, char** argv)
{
    string text = "token  test\tstring";

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

สิ่งนี้ทำสำเนาของโทเค็นทั้งหมดหรือไม่หรือเก็บเฉพาะตำแหน่งเริ่มต้นและจุดสิ้นสุดของโทเค็นปัจจุบันเท่านั้น
einpoklum

66

ฉันชอบสิ่งต่อไปนี้เนื่องจากใส่ผลลัพธ์ลงในเวกเตอร์สนับสนุนสตริงเป็น delim และให้การควบคุมการเก็บค่าว่าง แต่มันก็ดูไม่ดีเท่าที่ควร

#include <ostream>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>
using namespace std;

vector<string> split(const string& s, const string& delim, const bool keep_empty = true) {
    vector<string> result;
    if (delim.empty()) {
        result.push_back(s);
        return result;
    }
    string::const_iterator substart = s.begin(), subend;
    while (true) {
        subend = search(substart, s.end(), delim.begin(), delim.end());
        string temp(substart, subend);
        if (keep_empty || !temp.empty()) {
            result.push_back(temp);
        }
        if (subend == s.end()) {
            break;
        }
        substart = subend + delim.size();
    }
    return result;
}

int main() {
    const vector<string> words = split("So close no matter how far", " ");
    copy(words.begin(), words.end(), ostream_iterator<string>(cout, "\n"));
}

แน่นอนว่า Boost มีส่วนsplit()ที่ทำงานบางส่วนเช่นนั้น และถ้าโดย 'พื้นที่สีขาว' คุณหมายถึงพื้นที่สีขาวประเภทใดก็ได้จริง ๆ โดยใช้การแบ่งของ Boost กับการis_any_of()ทำงานที่ยอดเยี่ยม


ในที่สุดโซลูชันที่จัดการโทเค็นที่ว่างเปล่าอย่างถูกต้องที่ทั้งสองด้านของสตริง
fmuecke

53

STL ไม่มีวิธีการดังกล่าวอยู่แล้ว

อย่างไรก็ตามคุณสามารถใช้strtok()ฟังก์ชั่นของ C โดยใช้std::string::c_str()สมาชิกหรือคุณสามารถเขียนของคุณเอง นี่คือตัวอย่างรหัสที่ฉันพบหลังจากการค้นหาโดย Google อย่างรวดเร็ว ( "การแยกสตริง STL" ):

void Tokenize(const string& str,
              vector<string>& tokens,
              const string& delimiters = " ")
{
    // 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);
    }
}

นำมาจาก: http://oopweb.com/CPP/Documents/CPPHOWTO/Volume/C++Programming-HOWTO-7.html

หากคุณมีคำถามเกี่ยวกับตัวอย่างโค้ดฝากความคิดเห็นและฉันจะอธิบาย

และเพียงเพราะมันไม่ได้ใช้ตัวtypedefเรียกซ้ำหรือเกินตัว<<ดำเนินการไม่ได้หมายความว่ามันเป็นรหัสที่ไม่ดี ฉันใช้ฟังก์ชั่น C ค่อนข้างบ่อย ตัวอย่างเช่นprintfและscanfทั้งคู่เร็วกว่าstd::cinและstd::cout(สำคัญ) fopenไวยากรณ์จะเป็นมิตรมากขึ้นสำหรับประเภทไบนารีและพวกเขาก็มีแนวโน้มที่จะผลิต EXE ที่เล็กลง

อย่าขายในข้อตกลง"ความสง่างามเหนือประสิทธิภาพ"นี้


ฉันตระหนักถึงฟังก์ชั่นสตริง C และฉันก็ตระหนักถึงปัญหาด้านประสิทธิภาพด้วยเช่นกัน อย่างไรก็ตามสำหรับคำถามเฉพาะนี้ฉันกำลังมองหาโซลูชัน C ++ ที่สง่างาม
Ashwin Nanjappa

11
@Nelson LaQuet: ฉันเดา: เพราะ strtok ไม่ใช่ reentrant?
paercebal

40
@Nelson ไม่เคยผ่าน string.c_str () ไปยัง strtok! strtok trashes สตริงอินพุต (แทรกอักขระ '\ 0' เพื่อแทนที่แต่ละตัวคั่น foudn) และ c_str () ส่งคืนสตริงที่ไม่สามารถแก้ไขได้
Evan Teran

3
@ เนลสัน: อาเรย์นั้นจะต้องมีขนาด str.size () + 1 ในความคิดเห็นสุดท้ายของคุณ แต่ฉันเห็นด้วยกับวิทยานิพนธ์ของคุณว่ามันโง่ที่จะหลีกเลี่ยงฟังก์ชัน C ด้วยเหตุผล "สุนทรียะ"
j_random_hacker

2
@paulm: ไม่ความช้าของสตรีม C ++ นั้นเกิดจาก facets มันยังช้ากว่าฟังก์ชั่น stdio.h แม้ว่าจะปิดใช้งานการซิงโครไนซ์ (และใน stringstreams ซึ่งไม่สามารถซิงโครไนซ์ได้)
Ben Voigt

42

นี่คือฟังก์ชั่นแยกที่:

  • เป็นเรื่องทั่วไป
  • ใช้ C ++ มาตรฐาน (ไม่เพิ่ม)
  • ยอมรับตัวคั่นหลายตัว
  • ละเว้นโทเค็นที่ว่างเปล่า (สามารถเปลี่ยนได้อย่างง่ายดาย)

    template<typename T>
    vector<T> 
    split(const T & str, const T & delimiters) {
        vector<T> v;
        typename T::size_type start = 0;
        auto pos = str.find_first_of(delimiters, start);
        while(pos != T::npos) {
            if(pos != start) // ignore empty tokens
                v.emplace_back(str, start, pos - start);
            start = pos + 1;
            pos = str.find_first_of(delimiters, start);
        }
        if(start < str.length()) // ignore trailing delimiter
            v.emplace_back(str, start, str.length() - start); // add what's left of the string
        return v;
    }

ตัวอย่างการใช้งาน:

    vector<string> v = split<string>("Hello, there; World", ";,");
    vector<wstring> v = split<wstring>(L"Hello, there; World", L";,");

คุณลืมที่จะเพิ่มลงในรายการใช้งาน: "ไม่มีประสิทธิภาพอย่างยิ่ง"
Xander Tulip

1
@ XanderTulip คุณสามารถสร้างสรรค์มากขึ้นและอธิบายได้อย่างไรหรือทำไม?
Marco M.

3
@ XanderTulip: ฉันคิดว่าคุณกำลังอ้างอิงถึงการคืนค่าเวกเตอร์ด้วยค่า Return-Value-Optimization (RVO, google it) ควรดูแลสิ่งนี้ นอกจากนี้ใน C ++ 11 คุณสามารถส่งคืนได้โดยการอ้างอิงการย้าย
Joseph Garvin

3
สิ่งนี้สามารถปรับให้เหมาะสมต่อไปได้จริง: แทนที่จะ. push_back (str.substr (... )) สามารถใช้. emplace_back (str, start, pos - start) วิธีนี้วัตถุสตริงถูกสร้างขึ้นในคอนเทนเนอร์และทำให้เราหลีกเลี่ยงการดำเนินการย้าย + shenanigans อื่น ๆ ที่ทำโดยฟังก์ชั่น. substr
Mihai Bişog

@zoopp ใช่ ความคิดที่ดี. VS10 ไม่รองรับ emplace_back เมื่อฉันเขียนสิ่งนี้ ฉันจะอัปเดตคำตอบของฉัน ขอบคุณ
Marco M.

36

ฉันมีวิธีแก้ปัญหา 2 บรรทัดสำหรับปัญหานี้:

char sep = ' ';
std::string s="1 This is an example";

for(size_t p=0, q=0; p!=s.npos; p=q)
  std::cout << s.substr(p+(p!=0), (q=s.find(sep, p+1))-p-(p!=0)) << std::endl;

จากนั้นแทนที่จะพิมพ์คุณสามารถใส่ลงในเวกเตอร์


35

อีกวิธีที่ยืดหยุ่นและรวดเร็ว

template<typename Operator>
void tokenize(Operator& op, const char* input, const char* delimiters) {
  const char* s = input;
  const char* e = s;
  while (*e != 0) {
    e = s;
    while (*e != 0 && strchr(delimiters, *e) == 0) ++e;
    if (e - s > 0) {
      op(s, e - s);
    }
    s = e + 1;
  }
}

วิธีใช้กับเวกเตอร์ของสตริง (แก้ไข: เนื่องจากมีคนชี้ให้เห็นว่าไม่สืบทอดคลาส STL ... hrmf;)):

template<class ContainerType>
class Appender {
public:
  Appender(ContainerType& container) : container_(container) {;}
  void operator() (const char* s, unsigned length) { 
    container_.push_back(std::string(s,length));
  }
private:
  ContainerType& container_;
};

std::vector<std::string> strVector;
Appender v(strVector);
tokenize(v, "A number of words to be tokenized", " \t");

แค่นั้นแหละ! และนี่เป็นวิธีเดียวในการใช้ tokenizer เช่นเดียวกับวิธีนับจำนวนคำ:

class WordCounter {
public:
  WordCounter() : noOfWords(0) {}
  void operator() (const char*, unsigned) {
    ++noOfWords;
  }
  unsigned noOfWords;
};

WordCounter wc;
tokenize(wc, "A number of words to be counted", " \t"); 
ASSERT( wc.noOfWords == 7 );

ถูก จำกัด ด้วยจินตนาการ;)



32

นี่เป็นวิธีง่ายๆที่ใช้เฉพาะไลบรารีมาตรฐาน regex

#include <regex>
#include <string>
#include <vector>

std::vector<string> Tokenize( const string str, const std::regex regex )
{
    using namespace std;

    std::vector<string> result;

    sregex_token_iterator it( str.begin(), str.end(), regex, -1 );
    sregex_token_iterator reg_end;

    for ( ; it != reg_end; ++it ) {
        if ( !it->str().empty() ) //token could be empty:check
            result.emplace_back( it->str() );
    }

    return result;
}

อาร์กิวเมนต์ regex อนุญาตให้ตรวจสอบอาร์กิวเมนต์หลายตัว (ช่องว่างเครื่องหมายจุลภาค ฯลฯ )

ฉันมักจะตรวจสอบเพื่อแยกช่องว่างและเครื่องหมายจุลภาคดังนั้นฉันจึงมีฟังก์ชั่นเริ่มต้นนี้:

std::vector<string> TokenizeDefault( const string str )
{
    using namespace std;

    regex re( "[\\s,]+" );

    return Tokenize( str, re );
}

การ"[\\s,]+"ตรวจสอบช่องว่าง ( \\s) และเครื่องหมายจุลภาค ( ,)

หมายเหตุถ้าคุณต้องการที่จะแยกwstringแทนstring,

  • เปลี่ยนทั้งหมดstd::regexเป็นstd::wregex
  • เปลี่ยนทั้งหมดsregex_token_iteratorเป็นwsregex_token_iterator

หมายเหตุคุณอาจต้องการรับอาร์กิวเมนต์สตริงโดยอ้างอิงขึ้นอยู่กับคอมไพเลอร์ของคุณ


นี่จะเป็นคำตอบที่ฉันโปรดปราน แต่ std :: regex นั้นขาดใน GCC 4.8 พวกเขาบอกว่าพวกเขาใช้มันอย่างถูกต้องใน GCC 4.9 ฉันยังให้ +1 ของฉันกับคุณด้วย
mchiasson

1
นี่คือสิ่งที่ฉันโปรดปรานด้วยการเปลี่ยนแปลงเล็กน้อย: vector ส่งคืนเป็นข้อมูลอ้างอิงตามที่คุณพูดและอาร์กิวเมนต์ "str" ​​และ "regex" ส่งผ่านโดยบุคคลอ้างอิงด้วย ขอบคุณ.
QuantumKarl

1
สตริงดิบค่อนข้างมีประโยชน์ในขณะที่จัดการกับรูปแบบ regex วิธีการที่คุณจะได้ไม่ต้องใช้ลำดับหนี ... R"([\s,]+)"คุณก็สามารถใช้
Sam

26

ใช้งานได้std::stringstreamตามที่คุณต้องการทำงานได้อย่างสมบูรณ์แบบและทำสิ่งที่คุณต้องการ หากคุณกำลังมองหาวิธีที่แตกต่างของการทำสิ่งแม้ว่าคุณสามารถใช้std::find()/ และstd::find_first_of()std::string::substr()

นี่คือตัวอย่าง:

#include <iostream>
#include <string>

int main()
{
    std::string s("Somewhere down the road");
    std::string::size_type prev_pos = 0, pos = 0;

    while( (pos = s.find(' ', pos)) != std::string::npos )
    {
        std::string substring( s.substr(prev_pos, pos-prev_pos) );

        std::cout << substring << '\n';

        prev_pos = ++pos;
    }

    std::string substring( s.substr(prev_pos, pos-prev_pos) ); // Last word
    std::cout << substring << '\n';

    return 0;
}

ใช้ได้กับตัวคั่นอักขระเดียวเท่านั้น การเปลี่ยนแปลงอย่างง่ายช่วยให้สามารถทำงานกับหลายตัวละคร:prev_pos = pos += delimiter.length();
David Doria

25

หากคุณต้องการที่จะใช้เพิ่ม แต่ต้องการใช้สตริงทั้งเป็นตัวคั่น (แทนที่จะเป็นตัวละครเดียวในส่วนของโซลูชั่นที่นำเสนอก่อนหน้านี้), boost_split_iteratorคุณสามารถใช้

รหัสตัวอย่างรวมถึงแม่แบบที่สะดวกสบาย:

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

template<typename _OutputIterator>
inline void split(
    const std::string& str, 
    const std::string& delim, 
    _OutputIterator result)
{
    using namespace boost::algorithm;
    typedef split_iterator<std::string::const_iterator> It;

    for(It iter=make_split_iterator(str, first_finder(delim, is_equal()));
            iter!=It();
            ++iter)
    {
        *(result++) = boost::copy_range<std::string>(*iter);
    }
}

int main(int argc, char* argv[])
{
    using namespace std;

    vector<string> splitted;
    split("HelloFOOworldFOO!", "FOO", back_inserter(splitted));

    // or directly to console, for example
    split("HelloFOOworldFOO!", "FOO", ostream_iterator<string>(cout, "\n"));
    return 0;
}

20

เป็นโซลูชัน regex ที่ใช้ไลบรารี regex มาตรฐานเท่านั้น (ฉันเป็นสนิมเล็กน้อยดังนั้นอาจมีข้อผิดพลาดทางไวยากรณ์เล็กน้อย แต่อย่างน้อยก็เป็นความคิดทั่วไป)

#include <regex.h>
#include <string.h>
#include <vector.h>

using namespace std;

vector<string> split(string s){
    regex r ("\\w+"); //regex matches whole words, (greedy, so no fragment words)
    regex_iterator<string::iterator> rit ( s.begin(), s.end(), r );
    regex_iterator<string::iterator> rend; //iterators to iterate thru words
    vector<string> result<regex_iterator>(rit, rend);
    return result;  //iterates through the matches to fill the vector
}

การตอบสนองที่คล้ายกันกับวิธีการอาจจะ regex ที่ดีกว่า: ที่นี่และที่นี่
โนเบิล

20

strtokมีฟังก์ชั่นชื่อเป็น

#include<string>
using namespace std;

vector<string> split(char* str,const char* delim)
{
    char* saveptr;
    char* token = strtok_r(str,delim,&saveptr);

    vector<string> result;

    while(token != NULL)
    {
        result.push_back(token);
        token = strtok_r(NULL,delim,&saveptr);
    }
    return result;
}

3
strtokมาจากไลบรารีมาตรฐาน C ไม่ใช่ C ++ ไม่ปลอดภัยที่จะใช้ในโปรแกรมแบบมัลติเธรด มันแก้ไขสตริงอินพุต
Kevin Panko

13
เนื่องจากมันเก็บตัวชี้ถ่านจากการโทรครั้งแรกในตัวแปรแบบคงที่ดังนั้นในการโทรครั้งต่อไปเมื่อ NULL ถูกส่งผ่านมันจะจดจำตัวชี้ที่ควรใช้ หากเธรดที่สองเรียกใช้strtokเมื่อเธรดอื่นยังคงประมวลผลตัวชี้อักขระ char นี้จะถูกเขียนทับและเธรดทั้งสองจะมีผลลัพธ์ที่ไม่ถูกต้อง mkssoftware.com/docs/man3/strtok.3.asp
Kevin Panko

1
ดังที่ได้กล่าวมาก่อน strtok ไม่ปลอดภัยและแม้แต่ใน C strtok_r แนะนำให้ใช้
systemsfault

4
strtok_r สามารถใช้ได้ถ้าคุณอยู่ในส่วนของรหัสที่อาจเข้าถึงได้ นี่เป็นทางออกเดียวของทั้งหมดที่กล่าวมาข้างต้นซึ่งไม่ใช่ "สัญญาณรบกวนเส้น" และเป็นเครื่องพิสูจน์ถึงสิ่งที่ผิดกับ c ++ อย่างแน่นอน
Erik Aronesty

อัปเดตแล้วจึงไม่สามารถคัดค้านความปลอดภัยของเธรดได้จาก C ++ wonks
Erik Aronesty

17

การสตรีมสามารถทำได้สะดวกถ้าคุณต้องการแยกสตริงด้วยสัญลักษณ์ที่ไม่เว้นวรรค:

string s = "Name:JAck; Spouse:Susan; ...";
string dummy, name, spouse;

istringstream iss(s);
getline(iss, dummy, ':');
getline(iss, name, ';');
getline(iss, dummy, ':');
getline(iss, spouse, ';')

14

จนถึงตอนนี้ฉันใช้สิ่งนั้นในBoostแต่ฉันต้องการบางสิ่งที่ไม่ขึ้นอยู่กับมันดังนั้นฉันจึงมาที่นี่:

static void Split(std::vector<std::string>& lst, const std::string& input, const std::string& separators, bool remove_empty = true)
{
    std::ostringstream word;
    for (size_t n = 0; n < input.size(); ++n)
    {
        if (std::string::npos == separators.find(input[n]))
            word << input[n];
        else
        {
            if (!word.str().empty() || !remove_empty)
                lst.push_back(word.str());
            word.str("");
        }
    }
    if (!word.str().empty() || !remove_empty)
        lst.push_back(word.str());
}

ข้อดีคือseparatorsคุณสามารถผ่านตัวละครได้มากกว่าหนึ่งตัว


13

ฉันใช้สแตรคของตนเองแล้วใช้บูสต์เพื่อแยกสตริง วิธีที่ดีที่สุดที่ฉันได้พบเป็นc ++ String Toolkit ห้องสมุด มีความยืดหยุ่นและรวดเร็วอย่างไม่น่าเชื่อ

#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;
}

ชุดเครื่องมือมีความยืดหยุ่นมากกว่าตัวอย่างง่ายๆนี้แสดงให้เห็น แต่ยูทิลิตี้ในการแยกสตริงเป็นองค์ประกอบที่มีประโยชน์นั้นเหลือเชื่อมาก


13

สั้นและสง่างาม

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

vector<string> split(string data, string token)
{
    vector<string> output;
    size_t pos = string::npos; // size_t to avoid improbable overflow
    do
    {
        pos = data.find(token);
        output.push_back(data.substr(0, pos));
        if (string::npos != pos)
            data = data.substr(pos + token.size());
    } while (string::npos != pos);
    return output;
}

สามารถใช้สตริงใด ๆ เป็นตัวคั่นนอกจากนี้ยังสามารถใช้กับข้อมูลไบนารีได้ (std :: string รองรับข้อมูลไบนารีรวมถึงโมฆะ)

โดยใช้:

auto a = split("this!!is!!!example!string", "!!");

เอาท์พุท:

this
is
!example!string

1
ฉันชอบโซลูชันนี้เพราะอนุญาตให้ตัวแยกเป็นสตริงและไม่ใช่อักขระถ่านอย่างไรก็ตามมันกำลังแก้ไขตำแหน่งสตริงดังนั้นจึงบังคับให้สร้างสำเนาของสตริงเดิม
Alessandro Teruzzi

11

ฉันทำสิ่งนี้เพราะฉันต้องการวิธีที่ง่ายในการแยกสตริงและสตริงที่ใช้ c ... หวังว่าคนอื่นจะพบว่ามีประโยชน์เช่นกัน นอกจากนี้ยังไม่ต้องใช้โทเค็นและคุณสามารถใช้ฟิลด์เป็นตัวคั่นซึ่งเป็นอีกคีย์ที่ฉันต้องการ

ฉันแน่ใจว่ามีการปรับปรุงที่สามารถปรับปรุงให้ดียิ่งขึ้นได้และโปรดทำทุกวิถีทาง

StringSplitter.hpp:

#include <vector>
#include <iostream>
#include <string.h>

using namespace std;

class StringSplit
{
private:
    void copy_fragment(char*, char*, char*);
    void copy_fragment(char*, char*, char);
    bool match_fragment(char*, char*, int);
    int untilnextdelim(char*, char);
    int untilnextdelim(char*, char*);
    void assimilate(char*, char);
    void assimilate(char*, char*);
    bool string_contains(char*, char*);
    long calc_string_size(char*);
    void copy_string(char*, char*);

public:
    vector<char*> split_cstr(char);
    vector<char*> split_cstr(char*);
    vector<string> split_string(char);
    vector<string> split_string(char*);
    char* String;
    bool do_string;
    bool keep_empty;
    vector<char*> Container;
    vector<string> ContainerS;

    StringSplit(char * in)
    {
        String = in;
    }

    StringSplit(string in)
    {
        size_t len = calc_string_size((char*)in.c_str());
        String = new char[len + 1];
        memset(String, 0, len + 1);
        copy_string(String, (char*)in.c_str());
        do_string = true;
    }

    ~StringSplit()
    {
        for (int i = 0; i < Container.size(); i++)
        {
            if (Container[i] != NULL)
            {
                delete[] Container[i];
            }
        }
        if (do_string)
        {
            delete[] String;
        }
    }
};

StringSplitter.cpp:

#include <string.h>
#include <iostream>
#include <vector>
#include "StringSplit.hpp"

using namespace std;

void StringSplit::assimilate(char*src, char delim)
{
    int until = untilnextdelim(src, delim);
    if (until > 0)
    {
        char * temp = new char[until + 1];
        memset(temp, 0, until + 1);
        copy_fragment(temp, src, delim);
        if (keep_empty || *temp != 0)
        {
            if (!do_string)
            {
                Container.push_back(temp);
            }
            else
            {
                string x = temp;
                ContainerS.push_back(x);
            }

        }
        else
        {
            delete[] temp;
        }
    }
}

void StringSplit::assimilate(char*src, char* delim)
{
    int until = untilnextdelim(src, delim);
    if (until > 0)
    {
        char * temp = new char[until + 1];
        memset(temp, 0, until + 1);
        copy_fragment(temp, src, delim);
        if (keep_empty || *temp != 0)
        {
            if (!do_string)
            {
                Container.push_back(temp);
            }
            else
            {
                string x = temp;
                ContainerS.push_back(x);
            }
        }
        else
        {
            delete[] temp;
        }
    }
}

long StringSplit::calc_string_size(char* _in)
{
    long i = 0;
    while (*_in++)
    {
        i++;
    }
    return i;
}

bool StringSplit::string_contains(char* haystack, char* needle)
{
    size_t len = calc_string_size(needle);
    size_t lenh = calc_string_size(haystack);
    while (lenh--)
    {
        if (match_fragment(haystack + lenh, needle, len))
        {
            return true;
        }
    }
    return false;
}

bool StringSplit::match_fragment(char* _src, char* cmp, int len)
{
    while (len--)
    {
        if (*(_src + len) != *(cmp + len))
        {
            return false;
        }
    }
    return true;
}

int StringSplit::untilnextdelim(char* _in, char delim)
{
    size_t len = calc_string_size(_in);
    if (*_in == delim)
    {
        _in += 1;
        return len - 1;
    }

    int c = 0;
    while (*(_in + c) != delim && c < len)
    {
        c++;
    }

    return c;
}

int StringSplit::untilnextdelim(char* _in, char* delim)
{
    int s = calc_string_size(delim);
    int c = 1 + s;

    if (!string_contains(_in, delim))
    {
        return calc_string_size(_in);
    }
    else if (match_fragment(_in, delim, s))
    {
        _in += s;
        return calc_string_size(_in);
    }

    while (!match_fragment(_in + c, delim, s))
    {
        c++;
    }

    return c;
}

void StringSplit::copy_fragment(char* dest, char* src, char delim)
{
    if (*src == delim)
    {
        src++;
    }

    int c = 0;
    while (*(src + c) != delim && *(src + c))
    {
        *(dest + c) = *(src + c);
        c++;
    }
    *(dest + c) = 0;
}

void StringSplit::copy_string(char* dest, char* src)
{
    int i = 0;
    while (*(src + i))
    {
        *(dest + i) = *(src + i);
        i++;
    }
}

void StringSplit::copy_fragment(char* dest, char* src, char* delim)
{
    size_t len = calc_string_size(delim);
    size_t lens = calc_string_size(src);

    if (match_fragment(src, delim, len))
    {
        src += len;
        lens -= len;
    }

    int c = 0;
    while (!match_fragment(src + c, delim, len) && (c < lens))
    {
        *(dest + c) = *(src + c);
        c++;
    }
    *(dest + c) = 0;
}

vector<char*> StringSplit::split_cstr(char Delimiter)
{
    int i = 0;
    while (*String)
    {
        if (*String != Delimiter && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (*String == Delimiter)
        {
            assimilate(String, Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return Container;
}

vector<string> StringSplit::split_string(char Delimiter)
{
    do_string = true;

    int i = 0;
    while (*String)
    {
        if (*String != Delimiter && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (*String == Delimiter)
        {
            assimilate(String, Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return ContainerS;
}

vector<char*> StringSplit::split_cstr(char* Delimiter)
{
    int i = 0;
    size_t LenDelim = calc_string_size(Delimiter);

    while(*String)
    {
        if (!match_fragment(String, Delimiter, LenDelim) && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (match_fragment(String, Delimiter, LenDelim))
        {
            assimilate(String,Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return Container;
}

vector<string> StringSplit::split_string(char* Delimiter)
{
    do_string = true;
    int i = 0;
    size_t LenDelim = calc_string_size(Delimiter);

    while (*String)
    {
        if (!match_fragment(String, Delimiter, LenDelim) && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (match_fragment(String, Delimiter, LenDelim))
        {
            assimilate(String, Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return ContainerS;
}

ตัวอย่าง:

int main(int argc, char*argv[])
{
    StringSplit ss = "This:CUT:is:CUT:an:CUT:example:CUT:cstring";
    vector<char*> Split = ss.split_cstr(":CUT:");

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

จะส่งออก:

นี้
เป็น ตัวอย่าง CString



int main(int argc, char*argv[])
{
    StringSplit ss = "This:is:an:example:cstring";
    vector<char*> Split = ss.split_cstr(':');

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

int main(int argc, char*argv[])
{
    string mystring = "This[SPLIT]is[SPLIT]an[SPLIT]example[SPLIT]string";
    StringSplit ss = mystring;
    vector<string> Split = ss.split_string("[SPLIT]");

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

int main(int argc, char*argv[])
{
    string mystring = "This|is|an|example|string";
    StringSplit ss = mystring;
    vector<string> Split = ss.split_string('|');

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

ในการเก็บรายการที่ว่างเปล่า (โดยจะไม่รวมค่าเริ่มต้นเปล่า):

StringSplit ss = mystring;
ss.keep_empty = true;
vector<string> Split = ss.split_string(":DELIM:");

เป้าหมายคือการทำให้มันคล้ายกับวิธีการแยก () ของ C # ซึ่งการแยกสตริงเป็นเรื่องง่ายเหมือน:

String[] Split = 
    "Hey:cut:what's:cut:your:cut:name?".Split(new[]{":cut:"}, StringSplitOptions.None);

foreach(String X in Split)
{
    Console.Write(X);
}

ฉันหวังว่าคนอื่นจะพบว่าสิ่งนี้มีประโยชน์เหมือนฉัน


10

เกี่ยวกับสิ่งนี้:

#include <string>
#include <vector>

using namespace std;

vector<string> split(string str, const char delim) {
    vector<string> v;
    string tmp;

    for(string::const_iterator i; i = str.begin(); i <= str.end(); ++i) {
        if(*i != delim && i != str.end()) {
            tmp += *i; 
        } else {
            v.push_back(tmp);
            tmp = ""; 
        }   
    }   

    return v;
}

นี่คือคำตอบที่ดีที่สุดที่นี่ถ้าคุณต้องการแยกตัวอักษรตัวเดียว คำถามเดิมต้องการแยกในช่องว่างแม้ว่าหมายถึงการรวมกันของหนึ่งหรือมากกว่าหนึ่งช่องว่างหรือแท็บที่ต่อเนื่องกัน คุณได้ตอบจริงstackoverflow.com/questions/53849
Oktalist

10

คำตอบนี้ใช้สตริงและใส่ลงในเวกเตอร์ของสตริง มันใช้ห้องสมุดเพิ่ม

#include <boost/algorithm/string.hpp>
std::vector<std::string> strs;
boost::split(strs, "string to split", boost::is_any_of("\t "));

9

นี่เป็นอีกวิธีในการทำ ..

void split_string(string text,vector<string>& words)
{
  int i=0;
  char ch;
  string word;

  while(ch=text[i++])
  {
    if (isspace(ch))
    {
      if (!word.empty())
      {
        words.push_back(word);
      }
      word = "";
    }
    else
    {
      word += ch;
    }
  }
  if (!word.empty())
  {
    words.push_back(word);
  }
}

9

ฉันชอบที่จะใช้วิธีการเพิ่ม / regex สำหรับงานนี้เนื่องจากพวกเขาให้ความยืดหยุ่นสูงสุดสำหรับการระบุเกณฑ์การแยก

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

int main() {
    std::string line("A:::line::to:split");
    const boost::regex re(":+"); // one or more colons

    // -1 means find inverse matches aka split
    boost::sregex_token_iterator tokens(line.begin(),line.end(),re,-1);
    boost::sregex_token_iterator end;

    for (; tokens != end; ++tokens)
        std::cout << *tokens << std::endl;
}

9

เมื่อเร็ว ๆ นี้ฉันต้องแยกคำอูฐออกเป็น subwords ไม่มีตัวคั่นมีเพียงอักขระส่วนบน

#include <string>
#include <list>
#include <locale> // std::isupper

template<class String>
const std::list<String> split_camel_case_string(const String &s)
{
    std::list<String> R;
    String w;

    for (String::const_iterator i = s.begin(); i < s.end(); ++i) {  {
        if (std::isupper(*i)) {
            if (w.length()) {
                R.push_back(w);
                w.clear();
            }
        }
        w += *i;
    }

    if (w.length())
        R.push_back(w);
    return R;
}

ตัวอย่างเช่นการแยก "AQueryTrades" เป็น "A", "Query" และ "Trades" ฟังก์ชั่นนี้ใช้งานได้กับสตริงที่แคบและกว้าง เนื่องจากมันเคารพสถานที่ปัจจุบันจึงแยก "RaumfahrtÜberwachungsVerordnung" เป็น "Raumfahrt", "Überwachungs" และ "Verordnung"

หมายเหตุstd::upperควรจะถูกส่งผ่านจริงๆเป็นอาร์กิวเมนต์แม่แบบของฟังก์ชัน จากนั้นทั่วไปเพิ่มเติมจากฟังก์ชั่นนี้สามารถแยกตัวคั่นที่ชอบ",", ";"หรือ" "มากเกินไป


2
มี 2 ​​revs เยี่ยมมาก ดูเหมือนว่าภาษาอังกฤษของฉันต้องเป็น "เยอรมัน" มาก แต่เสียใหม่ไม่ได้รับการแก้ไขข้อบกพร่องเล็กน้อยสองอาจเพราะพวกเขาอยู่แล้วเห็นได้ชัด: อาจจะเป็นอาร์กิวเมนต์ไม่std::isupper std::upperประการที่สองใส่ก่อนtypename String::const_iterator
Andreas Spindler

9
#include<iostream>
#include<string>
#include<sstream>
#include<vector>
using namespace std;

    vector<string> split(const string &s, char delim) {
        vector<string> elems;
        stringstream ss(s);
        string item;
        while (getline(ss, item, delim)) {
            elems.push_back(item);
        }
        return elems;
    }

int main() {

        vector<string> x = split("thi is an sample test",' ');
        unsigned int i;
        for(i=0;i<x.size();i++)
            cout<<i<<":"<<x[i]<<endl;
        return 0;
}

9

การใช้std::string_viewและrange-v3ห้องสมุดของ Eric Niebler :

https://wandbox.org/permlink/kW5lwRCL1pxjp2pW

#include <iostream>
#include <string>
#include <string_view>
#include "range/v3/view.hpp"
#include "range/v3/algorithm.hpp"

int main() {
    std::string s = "Somewhere down the range v3 library";
    ranges::for_each(s  
        |   ranges::view::split(' ')
        |   ranges::view::transform([](auto &&sub) {
                return std::string_view(&*sub.begin(), ranges::distance(sub));
            }),
        [](auto s) {std::cout << "Substring: " << s << "\n";}
    );
}

โดยใช้ช่วงforลูปแทนranges::for_eachอัลกอริทึม:

#include <iostream>
#include <string>
#include <string_view>
#include "range/v3/view.hpp"

int main()
{
    std::string str = "Somewhere down the range v3 library";
    for (auto s : str | ranges::view::split(' ')
                      | ranges::view::transform([](auto&& sub) { return std::string_view(&*sub.begin(), ranges::distance(sub)); }
                      ))
    {
        std::cout << "Substring: " << s << "\n";
    }
}

Yepp ช่วงที่ดูดีกว่า - ฉันเห็นด้วย
Porsche9II
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.