2008-11-14 32 views
40

Tôi có một chuỗi mà tôi muốn tokenize. Nhưng hàm C strtok() yêu cầu chuỗi của tôi phải là char*. Tôi có thể làm điều này đơn giản như thế nào?Sử dụng strtok với một tiêu chuẩn :: string

tôi đã cố gắng:

token = strtok(str.c_str(), " "); 

mà không thành công vì nó biến nó thành một const char*, không phải là một char*

+0

Xem câu hỏi này: http://stackoverflow.com/questions/53849/how-do-i-tokenize-a-string-in-c/55680 – Ferruccio

Trả lời

15

Duplicate chuỗi, tokenize nó, sau đó giải phóng nó.

char *dup = strdup(str.c_str()); 
token = strtok(dup, " "); 
free(dup); 
+2

Không phải là câu hỏi tốt hơn, tại sao sử dụng strtok khi ngôn ngữ được đề cập có các tùy chọn gốc tốt hơn? –

+1

Không nhất thiết. Nếu ngữ cảnh của câu hỏi bao quanh việc duy trì một codebase mong manh, thì hãy tránh xa cách tiếp cận hiện tại (strtok trong ví dụ của tôi) là rủi ro hơn là thay đổi cách tiếp cận. Nếu không có nhiều bối cảnh trong câu hỏi, tôi thích trả lời những gì được hỏi. – DocMax

+0

Nếu người hỏi là người mới, bạn nên chống lại việc làm miễn phí() trước khi sử dụng mã thông báo ... :-) – PhiLho

1

Tôi cho rằng ngôn ngữ là C, hoặc C++ ...

strtok, IIRC, thay thế dải phân cách với \ 0. Đó là những gì nó không thể sử dụng một chuỗi const. Để giải quyết sự cố "nhanh", nếu chuỗi không lớn, bạn có thể chỉ strdup() nó. Đó là khôn ngoan nếu bạn cần phải giữ cho chuỗi không thay đổi gì (những gì const đề nghị ...).

Mặt khác, bạn có thể muốn sử dụng trình mã thông báo khác, có thể được cuộn bằng tay, ít bạo lực hơn đối số đã cho.

20
  1. Nếu boost có sẵn trên hệ thống của bạn (Tôi nghĩ đó là tiêu chuẩn trên hầu hết các Linux distro những ngày này), nó có một lớp Tokenizer bạn có thể sử dụng.

  2. Nếu không, thì Google nhanh chóng bật lên một số hand-rolled tokenizer cho std :: string mà bạn có thể chỉ cần sao chép và dán. Nó rất ngắn.

  3. Và nếu bạn không thích một trong số đó, thì đây là hàm split() tôi đã viết để làm cho cuộc sống của tôi dễ dàng hơn. Nó sẽ phá vỡ một chuỗi thành từng mảnh bằng cách sử dụng bất kỳ ký tự nào trong "delim" làm dấu tách. Mảnh được nối vào "bộ phận" vector:

    void split(const string& str, const string& delim, vector<string>& parts) { 
        size_t start, end = 0; 
        while (end < str.size()) { 
        start = end; 
        while (start < str.size() && (delim.find(str[start]) != string::npos)) { 
         start++; // skip initial whitespace 
        } 
        end = start; 
        while (end < str.size() && (delim.find(str[end]) == string::npos)) { 
         end++; // skip to end of word 
        } 
        if (end-start != 0) { // just ignore zero-length strings. 
         parts.push_back(string(str, start, end-start)); 
        } 
        } 
    } 
    
+0

+1 cho bộ mã thông báo được cuộn bằng tay. –

1

Giả sử rằng bằng "chuỗi" bạn đang nói về std :: string trong C++, bạn có thể có một cái nhìn tại các gói Tokenizer trong Boost.

57
#include <iostream> 
#include <string> 
#include <sstream> 
int main(){ 
    std::string myText("some-text-to-tokenize"); 
    std::istringstream iss(myText); 
    std::string token; 
    while (std::getline(iss, token, '-')) 
    { 
     std::cout << token << std::endl; 
    } 
    return 0; 
} 

Hoặc, như đã đề cập, hãy sử dụng tăng tính linh hoạt hơn.

2

CHỈNH SỬA: sử dụng const cast là chỉ được sử dụng để chứng minh hiệu ứng của strtok() khi áp dụng cho một con trỏ được trả về bởi chuỗi :: c_str().

Bạn không nên sử dụng strtok() vì nó làm thay đổi chuỗi tokenized có thể dẫn đến không mong muốn, nếu không xác định, hành vi như chuỗi C "thuộc" vào instance chuỗi.

#include <string> 
#include <iostream> 

int main(int ac, char **av) 
{ 
    std::string theString("hello world"); 
    std::cout << theString << " - " << theString.size() << std::endl; 

    //--- this cast *only* to illustrate the effect of strtok() on std::string 
    char *token = strtok(const_cast<char *>(theString.c_str()), " "); 

    std::cout << theString << " - " << theString.size() << std::endl; 

    return 0; 
} 

Sau khi cuộc gọi đến strtok(), không gian đã được "gỡ bỏ" từ chuỗi, hoặc từ chối để một nhân vật không thể in được, nhưng chiều dài vẫn không thay đổi.

>./a.out 
hello world - 11 
helloworld - 11 

Vì vậy, bạn phải sử dụng cơ chế gốc, sao chép chuỗi hoặc thư viện của bên thứ ba như đã đề cập trước đây.

+0

bỏ đi các const không giúp đỡ. Đó là const vì một lý do. –

+0

@Martin York: Đồng ý. Đó là const cho một lý do - xuống bình chọn. –

+1

@Martin York, @Sherm Pendley: bạn đã đọc kết luận hay chỉ đoạn mã? Tôi đã chỉnh sửa câu trả lời của mình để làm rõ những gì tôi muốn hiển thị ở đây. Rgds. – philant

0

Trước hết tôi sẽ nói sử dụng mã thông báo tăng cường.
Hoặc nếu dữ liệu của bạn được phân cách bằng dấu cách thì thư viện luồng chuỗi rất hữu ích.

Nhưng cả hai điều trên đều đã được đề cập đến.
Vì vậy, như là một thay thế C-Like thứ ba tôi đề xuất sao chép std :: string vào một bộ đệm để sửa đổi.

std::string data("The data I want to tokenize"); 

// Create a buffer of the correct length: 
std::vector<char> buffer(data.size()+1); 

// copy the string into the buffer 
strcpy(&buffer[0],data.c_str()); 

// Tokenize 
strtok(&buffer[0]," "); 
5

Có giải pháp thanh lịch hơn.

Với std :: string, bạn có thể sử dụng resize() để cấp phát bộ đệm lớn phù hợp và & s [0] để đưa con trỏ đến bộ đệm trong.

Tại thời điểm này, nhiều người tốt sẽ nhảy và hét lên trên màn hình. Nhưng đây là một thực tế. Khoảng 2 năm trước

nhóm làm việc thư viện đã quyết định (họp tại Lillehammer) giống như cho std :: vector, std :: string cũng nên chính thức, không chỉ trong thực tế, có bộ đệm liền kề được đảm bảo.

Sự quan tâm khác là strtok() làm tăng kích thước của chuỗi. Tài liệu MSDN cho biết:

Mỗi cuộc gọi đến strtok sẽ sửa đổi strToken bằng cách chèn ký tự rỗng sau mã thông báo được trả về bởi cuộc gọi đó.

Nhưng điều này không chính xác. Trên thực tế, hàm thay thế lần xuất hiện đầu tiên của ký tự phân tách bằng \ 0. Không thay đổi kích thước của chuỗi. Nếu chúng ta có chuỗi này:

One-Two --- 3-4

chúng tôi sẽ kết thúc với

một \ 0two \ 0 - ba \ 0 bốn

Vì vậy, giải pháp của tôi rất đơn giản:


std::string str("some-text-to-split"); 
char seps[] = "-"; 
char *token; 

token = strtok(&str[0], seps); 
while(token != NULL) 
{ 
    /* Do your thing */ 
    token = strtok(NULL, seps); 
} 

đọc thảo luận về http://www.archivum.info/comp.lang.c++/2008-05/02889/does_std::string_have_something_like_CString::GetBuffer

+0

-1. 'strtok()' hoạt động trên một chuỗi được kết thúc bằng null trong khi bộ đệm 'std :: string' không bắt buộc phải được kết thúc bằng null. Không có cách nào xung quanh 'c_str()'. – SnakE

+0

@SnakE 'std :: bộ đệm của chuỗi * * là * bắt buộc phải bị vô hiệu. 'dữ liệu' và' c_str' được yêu cầu phải giống hệt nhau và ['dữ liệu() + i == & toán tử [] (i)' cho mọi 'i' trong' [0, size()] '] (http: // en.cppreference.com/w/cpp/string/basic_string/c_str). – Leushenko

+0

@Leushenko bạn đã đúng một phần. Null-chấm dứt chỉ được đảm bảo kể từ C++ 11. Tôi đã thêm ghi chú vào câu trả lời. Tôi sẽ nâng -1 của tôi ngay khi chỉnh sửa của tôi được chấp nhận. – SnakE

0

Nếu bạn không quan tâm đến nguồn mở, bạn có thể sử dụng các lớp con và lớp con con từ https://github.com/EdgeCast/json_parser. Chuỗi gốc còn nguyên vẹn, không có phân bổ và không sao chép dữ liệu. Tôi đã không biên dịch những điều sau đây để có thể có lỗi.

std::string input_string("hello world"); 
subbuffer input(input_string); 
subparser flds(input, ' ', subparser::SKIP_EMPTY); 
while (!flds.empty()) 
{ 
    subbuffer fld = flds.next(); 
    // do something with fld 
} 

// or if you know it is only two fields 
subbuffer fld1 = input.before(' '); 
subbuffer fld2 = input.sub(fld1.length() + 1).ltrim(' '); 
1

Nó thất bại vì str.c_str() lợi nhuận chuỗi liên tục nhưng char * strtok (char * str, const char * delimiters) đòi hỏi chuỗi biến động. Vì vậy, bạn cần phải sử dụng * const_cast < char > để làm cho nó trở nên dễ bay hơi. Tôi cung cấp cho bạn một chương trình hoàn chỉnh nhưng nhỏ để mã hóa chuỗi bằng cách sử dụng hàm strtok() của C.

#include <iostream> 
#include <string> 
#include <string.h> 
using namespace std; 
int main() { 
    string s="20#6 5, 3"; 
    char *str=const_cast< char *>(s.c_str());  
    char *tok; 
    tok=strtok(str, "#, ");  
    int arr[4], i=0;  
    while(tok!=NULL){ 
     arr[i++]=stoi(tok); 
     tok=strtok(NULL, "#, "); 
    }  
    for(int i=0; i<4; i++) cout<<arr[i]<<endl; 
    return 0; 
} 
+1

FYI: strtok sẽ thay đổi giá trị của s. Bạn không nên sử dụng const_cast, vì điều này chỉ đơn giản là ẩn một vấn đề. – orbitcowboy

Các vấn đề liên quan