Làm cách nào để mã hóa một chuỗi trong C ++?


414

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 ++?


172
Tôi không thể tin rằng nhiệm vụ thường xuyên này là một vấn đề đau đầu như vậy trong c ++
wfbarksdale

6
Nó không đau đầu trong c ++ - có nhiều cách khác nhau để đạt được nó. lập trình viên ít biết về c ++ so với c # - marketing về đầu tư của mình ... thấy điều này cho nhiều c ++ tùy chọn để đạt được cùng: cplusplus.com/faq/sequences/strings/split
hB0

9
@ hB0 trải qua rất nhiều câu trả lời mà vẫn không quyết định phương tiện là một vấn đề đau đầu. một cái cần thư viện đó, cái kia chỉ dành cho không gian, cái kia không xử lý không gian ..
Paschalis

1
Có thể trùng lặp Chia chuỗi trong C ++?
KOB

2
Tại sao mọi thứ trong C ++ phải là một cuộc đấu tranh?
Wael Assaf

Câu trả lời:


145

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 splithà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::findcho đến khi bạn nhấn std::string::nposvà 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_iterators , 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_iteratorcho 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{}
);

53
Đáng buồn thay, boost không phải lúc nào cũng có sẵn cho tất cả các dự án. Tôi sẽ phải tìm kiếm một câu trả lời không tăng.
FuzzyBunnySlippers

36
Không phải mọi dự án đều mở cho "nguồn mở". Tôi làm việc trong các ngành công nghiệp quy định nặng. Đó thực sự không phải là một vấn đề. Đó chỉ là một thực tế của cuộc sống. Boost không có sẵn ở mọi nơi.
FuzzyBunnySlippers 20/12/13

5
@NonlinearIdeas Câu hỏi / câu trả lời khác hoàn toàn không phải về các dự án Nguồn mở. Điều này cũng đúng với bất kỳ dự án nào . Điều đó nói rằng, tất nhiên tôi hiểu về các tiêu chuẩn bị hạn chế như MISRA C nhưng sau đó nó hiểu rằng bạn xây dựng mọi thứ từ đầu (trừ khi bạn tình cờ tìm thấy một thư viện tuân thủ - rất hiếm). Dù sao đi nữa, vấn đề là hầu như không có sẵn Boost Boost - đó là bạn có những yêu cầu đặc biệt mà hầu như bất kỳ câu trả lời cho mục đích chung nào đều không phù hợp.
Konrad Rudolph

1
@NonlinearIdeas Trường hợp cụ thể, các câu trả lời khác, không Boost cũng không tuân thủ MISRA.
Konrad Rudolph

3
@Dmitry What STL barf '?! Và toàn bộ cộng đồng rất ủng hộ việc thay thế bộ tiền xử lý C - trên thực tế, có những đề xuất để làm điều đó. Nhưng đề nghị của bạn là sử dụng PHP hoặc một số ngôn ngữ khác thay vào đó sẽ là một bước lùi rất lớn.
Konrad Rudolph

188

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

1
Thứ tốt, gần đây tôi đã sử dụng điều này. Trình biên dịch Visual Studio của tôi có một số lẻ cho đến khi tôi sử dụng một khoảng trắng để phân tách hai ký tự ">" trước các bit (văn bản, sep) bit: (lỗi C2947: mong đợi '>' chấm dứt danh sách đối số khuôn mẫu, tìm thấy '> > ')
AndyUK

@AndyUK có, không có không gian, trình biên dịch sẽ phân tích cú pháp dưới dạng toán tử trích xuất thay vì hai mẫu đóng.
EnabrenTane

Về mặt lý thuyết đã được sửa trong C ++ 0x
David Souther

3
hãy cẩn thận với các tham số thứ ba của hàm char_separatortạo ( drop_empty_tokenslà mặc định, thay thế là keep_empty_tokens).
Benoit

5
@puk - Đây là hậu tố thường được sử dụng cho các tệp tiêu đề C ++. (như .hđối với tiêu đề C)
Ferruccio

167

Đâ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;
}

Tôi có cần thêm một nguyên mẫu cho phương thức này trong tệp .h không?
Suhrob Samiev

5
Đây không chính xác là câu trả lời "tốt nhất" vì nó vẫn sử dụng một chuỗi ký tự là mảng ký tự hằng số C đơn giản. Tôi tin rằng người hỏi đã hỏi liệu anh ta có thể mã hóa chuỗi C ++ thuộc loại "chuỗi" được giới thiệu sau không.
Vijay Kumar Kanta

Điều này cần một câu trả lời mới bởi vì tôi hoàn toàn nghi ngờ việc đưa vào các biểu thức chính quy trong C ++ 11 đã thay đổi câu trả lời tốt nhất sẽ là gì.
Omnifarious

113

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ã :-)


42
Không phải là tôi không thích C, tuy nhiên strtok không an toàn cho luồng và bạn cần chắc chắn rằng chuỗi bạn gửi nó chứa ký tự null để tránh tràn bộ đệm có thể xảy ra.
tloach

11
Có strtok_r, nhưng đây là một câu hỏi C ++.
Hợp đồng của giáo sư Falken vi phạm

3
@tloach: trong trình biên dịch MS C ++, strtok là luồng an toàn vì biến tĩnh bên trong được tạo trên TLS (lưu trữ cục bộ của luồng) (thực ra nó là trình biên dịch phụ thuộc)
Ahmed Said

3
@ahmed: thread safe có nghĩa là nhiều hơn là chỉ có thể chạy chức năng hai lần trong các luồng khác nhau. Trong trường hợp này nếu luồng được sửa đổi trong khi strtok đang chạy thì có thể chuỗi đó hợp lệ trong toàn bộ chuỗi strtok, nhưng strtok vẫn sẽ gây rối vì chuỗi đã thay đổi, giờ nó đã qua ký tự null và nó sẽ chuyển sang tiếp tục đọc bộ nhớ cho đến khi nó bị vi phạm bảo mật hoặc tìm thấy một ký tự null. Đây là một vấn đề với các hàm chuỗi C ban đầu, nếu bạn không chỉ định độ dài ở đâu đó bạn gặp sự cố.
tloach

4
strtok yêu cầu một con trỏ tới một mảng char không kết thúc null, đây không phải là một sinh vật phổ biến để tìm trong mã c ++ ... cách yêu thích của bạn để chuyển đổi thành chuỗi này từ chuỗi std :: là gì?
fuzzyTew

105

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.


2
Tôi gặp vấn đề khi sử dụng kỹ thuật này với các ký tự 0x0A trong chuỗi khiến vòng lặp while thoát sớm. Mặt khác, nó là một giải pháp đơn giản và nhanh chóng.
Ryan H.

4
Điều này là tốt nhưng chỉ cần lưu ý rằng bằng cách thực hiện điều này, dấu phân cách mặc định '\ n' không được xem xét. Ví dụ này sẽ hoạt động, nhưng nếu bạn đang sử dụng một cái gì đó như: while (getline (inFile, word, '')) trong đó inFile là đối tượng ifstream chứa nhiều dòng bạn sẽ nhận được kết quả
hài hước

đường dây xấu quá tệ trả về luồng thay vì chuỗi, khiến nó không thể sử dụng được trong danh sách khởi tạo mà không lưu trữ tạm thời
fuzzyTew

1
Mát mẻ! Không tăng và C ++ 11, giải pháp tốt cho các dự án cũ đó!
Đức Thanh

1
ĐÓ là câu trả lời, tên của hàm chỉ hơi khó xử.
Nils

82

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

17
Tôi thấy những std :: khó chịu khi đọc .. tại sao không sử dụng "bằng cách sử dụng"?
dùng35978

80
@Vadi: vì chỉnh sửa bài đăng của người khác khá khó chịu. @pheze: Tôi thích để stdtheo 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.
Matthieu M.

7
Tôi hiểu lý do của bạn và tôi nghĩ rằng nó thực sự là một lựa chọn tốt nếu nó phù hợp với bạn, nhưng từ quan điểm sư phạm tôi thực sự đồng ý với pheze. Ở đây dễ đọc và hiểu một ví dụ hoàn toàn xa lạ như ví dụ này với "sử dụng không gian tên std" ở đầu vì nó đòi hỏi ít nỗ lực hơn để diễn giải các dòng sau ... đặc biệt trong trường hợp này là vì mọi thứ đều từ thư viện chuẩn. Bạn có thể làm cho nó dễ đọc và rõ ràng nơi các đối tượng đến từ một loạt "sử dụng std :: string;" vv Đặc biệt là vì chức năng rất ngắn.
cheshirekow

61
Mặc dù các tiền tố "std ::" gây khó chịu hoặc xấu xí, tốt nhất nên đưa chúng vào mã ví dụ để nó hoàn toàn rõ ràng nơi các chức năng này đến từ đâu. Nếu họ làm phiền bạn, việc thay thế họ bằng "sử dụng" sau khi bạn lấy cắp ví dụ và tuyên bố nó là của bạn là chuyện nhỏ.
dlchambers

20
Vâng! những gì ông nói! thực hành tốt nhất là sử dụng tiền tố std. Bất kỳ cơ sở mã lớn nào chắc chắn sẽ có thư viện và không gian tên riêng và sử dụng "sử dụng không gian tên std" sẽ khiến bạn đau đầu khi bạn bắt đầu gây xung đột không gian tên.
Miek

48

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.)


12
Tại sao xác định một macro bạn chỉ sử dụng ở một nơi. Và làm thế nào là UASSERT của bạn tốt hơn so với khẳng định tiêu chuẩn. Chia tách so sánh thành 3 mã thông báo như thế không có gì khác ngoài yêu cầu nhiều dấu phẩy hơn bạn cần.
crelbor

1
Có thể macro UASSERT hiển thị (trong thông báo lỗi) mối quan hệ thực tế giữa (và giá trị của) hai giá trị được so sánh? Đó thực sự là một ý tưởng hay, IMHO.
GhassanPL

10
Ugh, tại sao std::stringlớp không bao gồm hàm split ()?
Ông Shickadance

Tôi nghĩ rằng dòng cuối cùng trong vòng lặp while nên 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.
John K

@JohnK Nếu đầu vào có hai dấu phân cách liên tiếp, thì rõ ràng chuỗi giữa chúng trống và nên được chèn vào vector. Nếu các giá trị trống không được chấp nhận cho một mục đích cụ thể, đó là một điều khác, nhưng IMHO các ràng buộc như vậy nên được thực thi bên ngoài loại chức năng mục đích rất chung này.
Lauri Nurmi

46

Một giải pháp sử dụng regex_token_iterators:

#include <iostream>
#include <regex>
#include <string>

using namespace std;

int main()
{
    string str("The quick brown fox");

    regex reg("\\s+");

    sregex_token_iterator iter(str.begin(), str.end(), reg, -1);
    sregex_token_iterator end;

    vector<string> vec(iter, end);

    for (auto a : vec)
    {
        cout << a << endl;
    }
}

5
Đây phải là câu trả lời xếp hạng hàng đầu. Đây là cách đúng đắn để làm điều này trong C ++> = 11.
Omnifarious

1
Tôi rất vui vì tôi đã cuộn hết câu trả lời này (hiện chỉ có 9 lượt upvote). Đây chính xác là mã của C ++ 11 cho nhiệm vụ này!
YePhIcK

Câu trả lời tuyệt vời không phụ thuộc vào các thư viện bên ngoài và sử dụng các thư viện đã có sẵn
Andrew

1
Câu trả lời tuyệt vời, mang lại sự linh hoạt nhất trong các dấu phân cách. Một vài lưu ý: Sử dụng \ s + regex sẽ tránh các mã thông báo trống ở giữa văn bản, nhưng sẽ cung cấp mã thông báo đầu tiên trống nếu văn bản bắt đầu bằng khoảng trắng. Ngoài ra, regex có vẻ chậm: trên máy tính xách tay của tôi, trong 20 MB văn bản ngẫu nhiên, phải mất 0,6 giây, so với 0,011 giây đối với strtok, strsep hoặc câu trả lời của Parham bằng cách sử dụng str.find_first_of hoặc 0,027 giây cho Perl hoặc 0,021 giây cho Python . Đối với văn bản ngắn, tốc độ có thể không phải là một mối quan tâm.
Mark Gates

2
Ok có thể nó trông rất tuyệt, nhưng điều này rõ ràng là quá lạm dụng các biểu thức thông thường. Chỉ hợp lý nếu bạn không quan tâm đến hiệu suất.
Marek R

35

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"
""

26

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


22

Đâ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());
}

19

Đây là một giải pháp đơn giản chỉ STL (~ 5 dòng!) Sử dụng std::findstd::find_first_not_ofxử 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 !


3
Đây là một cái tốt nhưng tôi nghĩ bạn cần sử dụng find_first_of () thay vì find () để nó hoạt động đúng với nhiều dấu phân cách.

2
@ user755921 nhiều dấu phân cách được bỏ qua khi tìm vị trí bắt đầu với find_first_not_of.
Người mới bắt đầu

16

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, "-");

3
Wow, bạn đã trả lời câu hỏi ngay lập tức của tôi và nhiều câu hỏi trong tương lai. Tôi nhận được rằng c ++ là mạnh mẽ. Nhưng khi tách một chuỗi kết quả trong mã nguồn như các câu trả lời ở trên, thì rõ ràng là không phù hợp. Tôi rất muốn biết các thư viện khác như thế này kéo các tiện ích langau cấp cao hơn xuống.
Ross

wow, bạn nghiêm túc làm cho ngày của tôi !! không biết về pystring. Điều này sẽ giúp tôi tiết kiệm rất nhiều thời gian!
tích lũy

11

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

8

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

1
Tôi sẽ làmwhile ( is >> tmps ) { std::cout << tmps << "\n"; }
jordix

6

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

1
Hàm Tokenize () này sẽ bỏ qua các mã thông báo trống, ví dụ: nếu có chuỗi con "%%" trong chuỗi chính, không có mã thông báo trống nào được trả về. Nó được bỏ qua.
Sheen

4

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ó.


3
Lưu ý rằng strtok sửa đổi chuỗi bạn đang kiểm tra, vì vậy bạn không thể sử dụng nó trên chuỗi const char * mà không tạo bản sao.
Graeme Perrow

9
Vấn đề đa luồng là strtok sử dụng biến toàn cục để theo dõi vị trí của nó, vì vậy nếu bạn có hai luồng mà mỗi luồng sử dụng strtok, bạn sẽ có hành vi không xác định.
JohnMcG

@JohnMcG Hoặc chỉ sử dụng strtok_svề cơ bản strtokvới trạng thái rõ ràng đi qua.
Matthias

4

Đố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.


4

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).


+1 để đề xuất regex, nếu bạn không cần tốc độ cong vênh thì đó là giải pháp linh hoạt nhất, chưa được hỗ trợ ở mọi nơi nhưng khi thời gian trôi qua sẽ trở nên ít quan trọng hơn.
odinthenerd

+1 từ tôi, chỉ cần thử <regex> trong c ++ 11. Thật đơn giản và thanh lịch
StahlRat

4

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

4

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;

1
Lỗi của tôi khi đưa ra một ví dụ xấu (quá đơn giản). Theo như tôi biết, nó chỉ hoạt động khi dấu phân cách của bạn là khoảng trắng.
Xuất hóa đơn cho thằn lằn

4

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 stringvò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;
}

Live Example


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ụ, strtokkhô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:

  1. strtokkhông thể được sử dụng trên nhiều lần stringscùng một lúc: nullptrPhải thông qua để tiếp tục mã hóa dòng điện stringhoặ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:)
  2. Vì cùng một lý do strtokkhô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 )
  3. Việc gọi strtoksẽ sửa đổi stringnó đang hoạt động, do đó, nó không thể được sử dụng trên const stringcác const char*chuỗi s, s hoặc bằng chữ, để mã hóa bất kỳ trong số này với strtokhoặc để hoạt động trên một stringnộ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ên

cung cấp cho chúng tôi split_viewmã 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 vectortạ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 đó 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>() };

Live Example

Việc xây dựng một yêu cầu istringstreamcho 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í stringphâ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_iteratorkhó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 stringchi 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() };

Live Example


strtok_sNhân tiện, là tiêu chuẩn C11. strtok_rlà tiêu chuẩn POSIX2001. Giữa cả hai, có một phiên bản tái đăng ký tiêu chuẩn strtokcho hầu hết các nền tảng.
Andon M. Coleman

@ AndonM.Coleman Nhưng đây là một câu hỏi c ++ và trong C ++ #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 strtoktiện ích mở rộng?
Jonathan Mee

1
Chỉ là nó không phi tiêu chuẩn như mọi người có thể tin. 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 _schức năng của Microsoft đã trở thành tiêu chuẩn C.
Andon M. Coleman

@ AndonM.Coleman Phải, tôi với bạn. Rõ ràng nếu trong tiêu chuẩn C11, giao diện và cách triển khai có các ràng buộc được đặt trên chúng đòi hỏi hành vi giống hệt nhau độc lập với nền tảng. Bây giờ vấn đề duy nhất là đảm bảo rằng chức năng C11 có sẵn cho chúng tôi trên các nền tảng. Hy vọng rằng tiêu chuẩn C11 sẽ là thứ mà C ++ 17 hoặc C ++ 20 chọn để chọn.
Jonathan Mee

3

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


Đã sử dụng giải pháp của bạn từ đám đông :) Tôi có thể sửa đổi mã của bạn để thêm bất kỳ dấu phân cách nào không?
Zac

1
@Zac rất vui vì bạn thích nó và bạn có thể sửa đổi nó ... chỉ cần thêm phần cập nhật được in đậm vào câu trả lời của tôi ...
NutCracker

2

Đâ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;
}

2

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 getNextTokenchứ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


1
Có thể tokennize với một dấu phân cách String không?
Galigator

phiên bản này chỉ được tối ưu hóa cho các dấu phân cách ký tự đơn, sử dụng bảng tra cứu không phù hợp với các dấu phân cách đa chuỗi (chuỗi) nên khó đánh bại regex về hiệu quả.
odinthenerd

1

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

1

Đâ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;
}

1
(Xuống) phiếu bầu có thể mang tính xây dựng như upvote, nhưng không phải khi bạn không để lại bình luận về lý do tại sao ...
kayleeFrye_onDeck

1
Tôi đã loại bạn ra nhưng có thể là do mã trông khá đáng ngại đối với lập trình viên đang 'tách chuỗi' đặc biệt là không có tài liệu
mattshu

Cảm ơn @mattshu! Có phải đó là phân khúc regex khiến nó nản chí hay cái gì khác?
kayleeFrye_onDeck

0

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

0

boost::tokenizerlà 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_tthay vì các di sản string/ charloạ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;
}

"Di sản" chắc chắn là không chính xác và wchar_tlà 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.
CoffeeandCode

Việc sử dụng wchar_t không bằng cách nào đó tự động giải quyết bất kỳ vấn đề i18n nào. Bạn sử dụng mã hóa để giải quyết vấn đề đó. Nếu bạn đang phân tách một chuỗi bằng một dấu phân cách, điều đó có nghĩa là dấu phân cách không xung đột với nội dung được mã hóa của bất kỳ mã thông báo nào bên trong chuỗi. Có thể cần phải trốn thoát, v.v. wchar_t không phải là một giải pháp kỳ diệu cho vấn đề này.
yonil

0

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;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.