Làm thế nào để strtok () chia chuỗi thành các mã thông báo trong C?


114

Xin vui lòng giải thích cho tôi hoạt động của strtok()chức năng. Sách hướng dẫn cho biết nó phá vỡ chuỗi thành các mã thông báo. Tôi không thể hiểu từ hướng dẫn sử dụng nó thực sự làm gì.

Tôi đã thêm đồng hồ vào str*pchđể kiểm tra hoạt động của nó khi vòng lặp while đầu tiên xảy ra, nội dung của strchỉ là "this". Kết quả hiển thị dưới đây được in trên màn hình như thế nào?

/* strtok example */
#include <stdio.h>
#include <string.h>

int main ()
{
  char str[] ="- This, a sample string.";
  char * pch;
  printf ("Splitting string \"%s\" into tokens:\n",str);
  pch = strtok (str," ,.-");
  while (pch != NULL)
  {
    printf ("%s\n",pch);
    pch = strtok (NULL, " ,.-");
  }
  return 0;
}

Đầu ra:

Chuỗi phân tách "- Đây, một chuỗi mẫu." thành các mã thông báo:
Điều này
a
mẫu vật
chuỗi

5
strtok()sửa đổi chuỗi đối số của nó bằng cách kết thúc mã thông báo bằng NUL trước khi trả về. Nếu bạn cố gắng kiểm tra toàn bộ vùng đệm (str []), bạn sẽ thấy nó được sửa đổi giữa các lần gọi liên tiếp tới strtok().
Michael Foukarakis

Thay vì xem str, đồng hồ str[0], str[1], str[2], ...
PMG

@pmg: Tôi đã xem str [0] và str [1] .str [1] phải là '\ 0', nhưng nó là một khoảng trắng ở đó.
fuddin

3
Thành thật mà nói, tôi chưa bao giờ bận tâm đến việc kiểm tra, nhưng tôi tưởng tượng nó lưu trữ con trỏ cuối cùng được chuyển vào, cùng với vị trí mà nó đã dừng lại. Sau đó, nó chỉ có thể tiếp tục nếu con trỏ là NULL, hoặc xóa vị trí và bắt đầu lại nếu không.
chris

7
@Firegun: biến tĩnh .
DCoder

Câu trả lời:


38

strtok()chia chuỗi thành các mã thông báo. tức là bắt đầu từ bất kỳ dấu phân cách nào đến dấu phân cách tiếp theo sẽ là một mã thông báo của bạn. Trong trường hợp của bạn, mã thông báo bắt đầu sẽ là từ "-" và kết thúc bằng dấu cách tiếp theo "". Sau đó, mã thông báo tiếp theo sẽ bắt đầu từ "" và kết thúc bằng ",". Ở đây bạn nhận được "Cái này" làm đầu ra. Tương tự, phần còn lại của chuỗi được chia thành các mã thông báo từ không gian này sang không gian khác và cuối cùng kết thúc mã thông báo cuối cùng trên "."


điều kiện kết thúc cho một mã thông báo trở thành mã thông báo bắt đầu của mã thông báo tiếp theo? Ngoài ra, có ký tự nul được đặt ở vị trí của điều kiện kết thúc không?
fuddin

1
@ fahad- Vâng, tất cả những gì bạn có sẽ được thay thế bằng ký tự NUL như những người khác cũng đã đề xuất.
Sachin Shanbhag

Nếu tất cả các dấu phân cách được thay thế bằng Nul, thì tại sao chuỗi lại chứa "-this"? Nó phải chứa "\ 0"
fuddin

2
@fahad - Nó chỉ thay thế các ký tự phân cách bằng NUL, không phải tất cả các ký tự giữa các dấu phân cách. Đây là loại chia chuỗi thành nhiều mã thông báo. Bạn nhận được "This" bởi vì nó nằm giữa hai dấu phân cách được chỉ định chứ không phải "-this".
Sachin Shanbhag

1
@Fahad - Đúng vậy. Tất cả các khoảng trắng, "," và "-" được thay thế bằng NUL vì bạn đã chỉ định chúng làm dấu phân cách, theo như tôi hiểu.
Sachin Shanbhag

212

hàm thời gian chạy strtok hoạt động như thế này

lần đầu tiên bạn gọi strtok, bạn cung cấp một chuỗi mà bạn muốn mã hóa

char s[] = "this is a string";

trong không gian chuỗi ở trên dường như là một dấu phân cách tốt giữa các từ, vì vậy hãy sử dụng điều đó:

char* p = strtok(s, " ");

điều xảy ra bây giờ là 's' được tìm kiếm cho đến khi tìm thấy ký tự khoảng trắng, mã thông báo đầu tiên được trả về ('this') và p trỏ đến mã thông báo đó (chuỗi)

để lấy mã thông báo tiếp theo và để tiếp tục với cùng một chuỗi, NULL được chuyển làm đối số đầu tiên vì strtok duy trì một con trỏ tĩnh đến chuỗi đã truyền trước đó của bạn:

p = strtok(NULL," ");

p bây giờ trỏ đến 'là'

và cứ tiếp tục như vậy cho đến khi không tìm thấy thêm khoảng trắng nào nữa, thì chuỗi cuối cùng được trả về dưới dạng mã thông báo cuối cùng là 'chuỗi'.

thuận tiện hơn, bạn có thể viết nó như thế này để in ra tất cả các mã thông báo:

for (char *p = strtok(s," "); p != NULL; p = strtok(NULL, " "))
{
  puts(p);
}

BIÊN TẬP:

Nếu bạn muốn lưu trữ các giá trị trả về, strtokbạn cần phải sao chép mã thông báo vào một bộ đệm khác, ví dụ như strdup(p);chuỗi gốc (được trỏ tới bởi con trỏ tĩnh bên trong strtok) được sửa đổi giữa các lần lặp để trả về mã thông báo.


Vì vậy, nó không thực sự đặt một ký tự nul giữa chuỗi? Tại sao đồng hồ của tôi hiển thị rằng chuỗi chỉ còn lại "NÀY"?
fuddin

4
nó thực sự thay thế '' nó được tìm thấy bằng '\ 0'. Và, nó không khôi phục '' sau đó, vì vậy chuỗi của bạn bị hủy hoại.

33
1 cho đệm tĩnh, đây là những gì tôi không hiểu
IEatBagels

1
Một chi tiết rất quan trọng, bị thiếu trong dòng "mã thông báo đầu tiên được trả lại và ptrỏ đến mã thông báo đó" , là strtokcần phải thay đổi chuỗi ban đầu bằng cách đặt các ký tự null thay cho dấu phân cách (nếu không các hàm chuỗi khác sẽ không biết ở đâu mã thông báo kết thúc). Và nó cũng theo dõi trạng thái bằng cách sử dụng một biến tĩnh.
Groo

@Groo Tôi nghĩ rằng tôi đã thêm điều đó trong Chỉnh sửa mà tôi đã thực hiện vào năm 2017, nhưng bạn nói đúng.
AndersK

25

strtokduy trì một tham chiếu nội bộ, tĩnh trỏ đến mã thông báo có sẵn tiếp theo trong chuỗi; nếu bạn chuyển nó một con trỏ NULL, nó sẽ hoạt động từ tham chiếu nội bộ đó.

Đây là lý do strtokkhông thể tham gia lại; ngay sau khi bạn chuyển cho nó một con trỏ mới, tham chiếu nội bộ cũ đó sẽ bị che khuất.


Ý bạn là gì khi tham chiếu nội bộ cũ 'bị che khuất'. Ý bạn là 'bị ghi đè'?
ylun.ca

1
@ ylun.ca: vâng, ý tôi là vậy.
John Bode

10

strtokkhông thay đổi chính tham số ( str). Nó lưu trữ con trỏ đó (trong một biến tĩnh cục bộ). Sau đó, nó có thể thay đổi thông số đó trỏ đến trong các cuộc gọi tiếp theo mà không cần truyền lại tham số. (Và nó có thể nâng cao con trỏ mà nó đã giữ, tuy nhiên nó cần thực hiện các hoạt động của mình.)

Từ strtoktrang POSIX :

Hàm này sử dụng lưu trữ tĩnh để theo dõi vị trí chuỗi hiện tại giữa các cuộc gọi.

Có một biến thể an toàn theo luồng ( strtok_r) không thực hiện loại phép thuật này.


2
Chà, các chức năng của thư viện C có niên đại từ khi nào, quá trình phân luồng hoàn toàn không có trong hình ảnh (chỉ bắt đầu tồn tại vào năm 2011 theo như tiêu chuẩn C có liên quan), vì vậy việc tái tạo không thực sự quan trọng ( Tôi đoán). Cục bộ tĩnh đó làm cho hàm "dễ sử dụng" (đối với một số định nghĩa về "dễ dàng"). Giống như ctimetrả về một chuỗi tĩnh - thực tế (không ai cần phải tự hỏi ai sẽ giải phóng nó), nhưng không đăng nhập lại và đưa bạn lên nếu bạn không biết rõ về nó.
Mat

Điều này là sai: " strtokkhông thay đổi chính tham số ( str)." puts(str);bản in "- Cái này" kể từ khi strtoksửa đổi str.
MarredCheese

1
@MarredCheese: đọc lại. Nó không sửa đổi con trỏ. Nó sửa đổi dữ liệu mà con trỏ trỏ tới (tức là dữ liệu chuỗi)
Mat

Ồ được rồi, tôi không nhận ra đó là những gì bạn nhận được. Đã đồng ý.
MarredCheese

8

Lần đầu tiên bạn gọi nó, bạn cung cấp chuỗi để mã hóa strtok. Và sau đó, để nhận các mã thông báo sau, bạn chỉ cần cung cấp cho NULLhàm đó, miễn là nó trả về một NULLcon trỏ không phải .

Các strtokchức năng ghi lại các chuỗi đầu tiên mà bạn cung cấp khi bạn gọi nó. (Điều này thực sự nguy hiểm đối với các ứng dụng đa luồng)


8

strtok sẽ mã hóa một chuỗi tức là chuyển đổi nó thành một chuỗi các chuỗi con.

Nó thực hiện điều đó bằng cách tìm kiếm các dấu phân cách phân tách các mã thông báo (hoặc chuỗi con) này. Và bạn chỉ định các dấu phân cách. Trong trường hợp của bạn, bạn muốn '' hoặc ',' hoặc '.' hoặc '-' để làm dấu phân cách.

Mô hình lập trình để trích xuất các mã thông báo này là bạn strtok chuỗi chính của mình và tập hợp các dấu phân cách. Sau đó, bạn gọi nó nhiều lần và mỗi lần strtok sẽ trả về mã thông báo tiếp theo mà nó tìm thấy. Cho đến khi nó đến cuối chuỗi chính, khi nó trả về giá trị null. Một quy tắc khác là bạn chỉ truyền chuỗi trong lần đầu tiên và NULL cho những lần sau. Đây là một cách để cho strtok biết nếu bạn đang bắt đầu một phiên mã hóa mới với một chuỗi mới hoặc bạn đang truy xuất mã thông báo từ một phiên mã hóa trước đó. Lưu ý rằng strtok ghi nhớ trạng thái của nó cho phiên mã hóa. Và vì lý do này, nó không được đăng nhập lại hoặc luồng an toàn (bạn nên sử dụng strtok_r để thay thế). Một điều khác cần biết là nó thực sự sửa đổi chuỗi gốc. Nó viết '\ 0' cho các dấu phân cách teh mà nó tìm thấy.

Một cách để gọi strtok, thành công, như sau:

char str[] = "this, is the string - I want to parse";
char delim[] = " ,-";
char* token;

for (token = strtok(str, delim); token; token = strtok(NULL, delim))
{
    printf("token=%s\n", token);
}

Kết quả:

this
is
the
string
I
want
to
parse

5

strtok sửa đổi chuỗi đầu vào của nó. Nó đặt các ký tự rỗng ('\ 0') vào đó để nó sẽ trả về các bit của chuỗi ban đầu dưới dạng mã thông báo. Thực tế strtok không cấp phát bộ nhớ. Bạn có thể hiểu rõ hơn nếu bạn vẽ chuỗi dưới dạng một chuỗi các hộp.


3

Để hiểu cách strtok()hoạt động, trước tiên, người ta cần biết biến tĩnh là gì. Liên kết này giải thích nó khá tốt ....

Chìa khóa cho hoạt động của strtok()là bảo toàn vị trí của trình phân tách cuối cùng giữa các cuộc gọi bảo mật (đó là lý do tại sao strtok()tiếp tục phân tích cú pháp chuỗi ban đầu được chuyển đến nó khi nó được gọi bằng một null pointertrong các cuộc gọi liên tiếp) ..

Hãy xem cách strtok()triển khai của riêng tôi , được gọi là zStrtok(), có chức năng hoàn toàn khác với chức năng được cung cấp bởistrtok()

char *zStrtok(char *str, const char *delim) {
    static char *static_str=0;      /* var to store last address */
    int index=0, strlength=0;           /* integers for indexes */
    int found = 0;                  /* check if delim is found */

    /* delimiter cannot be NULL
    * if no more char left, return NULL as well
    */
    if (delim==0 || (str == 0 && static_str == 0))
        return 0;

    if (str == 0)
        str = static_str;

    /* get length of string */
    while(str[strlength])
        strlength++;

    /* find the first occurance of delim */
    for (index=0;index<strlength;index++)
        if (str[index]==delim[0]) {
            found=1;
            break;
        }

    /* if delim is not contained in str, return str */
    if (!found) {
        static_str = 0;
        return str;
    }

    /* check for consecutive delimiters
    *if first char is delim, return delim
    */
    if (str[0]==delim[0]) {
        static_str = (str + 1);
        return (char *)delim;
    }

    /* terminate the string
    * this assignmetn requires char[], so str has to
    * be char[] rather than *char
    */
    str[index] = '\0';

    /* save the rest of the string */
    if ((str + index + 1)!=0)
        static_str = (str + index + 1);
    else
        static_str = 0;

        return str;
}

Và đây là một ví dụ sử dụng

  Example Usage
      char str[] = "A,B,,,C";
      printf("1 %s\n",zStrtok(s,","));
      printf("2 %s\n",zStrtok(NULL,","));
      printf("3 %s\n",zStrtok(NULL,","));
      printf("4 %s\n",zStrtok(NULL,","));
      printf("5 %s\n",zStrtok(NULL,","));
      printf("6 %s\n",zStrtok(NULL,","));

  Example Output
      1 A
      2 B
      3 ,
      4 ,
      5 C
      6 (null)

Mã là từ một thư viện xử lý chuỗi mà tôi duy trì trên Github , được gọi là zString. Hãy xem mã, hoặc thậm chí đóng góp :) https://github.com/fnoyanisi/zString


3

Đây là cách tôi thực hiện strtok, Không tuyệt vời lắm nhưng sau 2 giờ làm việc với nó cuối cùng nó đã hoạt động. Nó hỗ trợ nhiều dấu phân cách.

#include "stdafx.h"
#include <iostream>
using namespace std;

char* mystrtok(char str[],char filter[]) 
{
    if(filter == NULL) {
        return str;
    }
    static char *ptr = str;
    static int flag = 0;
    if(flag == 1) {
        return NULL;
    }
    char* ptrReturn = ptr;
    for(int j = 0; ptr != '\0'; j++) {
        for(int i=0 ; filter[i] != '\0' ; i++) {
            if(ptr[j] == '\0') {
                flag = 1;
                return ptrReturn;
            }
            if( ptr[j] == filter[i]) {
                ptr[j] = '\0';
                ptr+=j+1;
                return ptrReturn;
            }
        }
    }
    return NULL;
}

int _tmain(int argc, _TCHAR* argv[])
{
    char str[200] = "This,is my,string.test";
    char *ppt = mystrtok(str,", .");
    while(ppt != NULL ) {
        cout<< ppt << endl;
        ppt = mystrtok(NULL,", ."); 
    }
    return 0;
}


1

Đây là cách triển khai của tôi sử dụng bảng băm cho dấu phân cách, có nghĩa là nó O (n) thay vì O (n ^ 2) (đây là một liên kết đến mã) :

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#define DICT_LEN 256

int *create_delim_dict(char *delim)
{
    int *d = (int*)malloc(sizeof(int)*DICT_LEN);
    memset((void*)d, 0, sizeof(int)*DICT_LEN);

    int i;
    for(i=0; i< strlen(delim); i++) {
        d[delim[i]] = 1;
    }
    return d;
}



char *my_strtok(char *str, char *delim)
{

    static char *last, *to_free;
    int *deli_dict = create_delim_dict(delim);

    if(!deli_dict) {
        /*this check if we allocate and fail the second time with entering this function */
        if(to_free) {
            free(to_free);
        }
        return NULL;
    }

    if(str) {
        last = (char*)malloc(strlen(str)+1);
        if(!last) {
            free(deli_dict);
            return NULL;
        }
        to_free = last;
        strcpy(last, str);
    }

    while(deli_dict[*last] && *last != '\0') {
        last++;
    }
    str = last;
    if(*last == '\0') {
        free(deli_dict);
        free(to_free);
        deli_dict = NULL;
        to_free = NULL;
        return NULL;
    }
    while (*last != '\0' && !deli_dict[*last]) {
        last++;
    }

    *last = '\0';
    last++;

    free(deli_dict);
    return str;
}

int main()
{
    char * str = "- This, a sample string.";
    char *del = " ,.-";
    char *s = my_strtok(str, del);
    while(s) {
        printf("%s\n", s);
        s = my_strtok(NULL, del);
    }
    return 0;
}

1

strtok () lưu trữ con trỏ trong biến tĩnh mà lần trước bạn đã dừng lại, vì vậy trong lần gọi thứ 2, khi chúng ta chuyển giá trị null, strtok () sẽ lấy con trỏ từ biến tĩnh.

Nếu bạn cung cấp cùng một tên chuỗi, nó sẽ bắt đầu lại từ đầu.

Hơn nữa, strtok () là hủy nghĩa là nó thực hiện các thay đổi đối với chuỗi gốc. vì vậy hãy đảm bảo rằng bạn luôn có một bản sao của bản gốc.

Một vấn đề nữa khi sử dụng strtok () là vì nó lưu trữ địa chỉ trong các biến tĩnh, trong lập trình đa luồng gọi strtok () nhiều lần sẽ gây ra lỗi. Đối với điều này, hãy sử dụng strtok_r ().


0

Đối với những ai vẫn còn khó hiểu về strtok()chức năng này , hãy xem ví dụ về pythontutor này , nó là một công cụ tuyệt vời để hình dung mã C (hoặc C ++, Python ...) của bạn.

Trong trường hợp liên kết bị hỏng, hãy dán vào:

#include <stdio.h>
#include <string.h>

int main()
{
    char s[] = "Hello, my name is? Matthew! Hey.";
    char* p;
    for (char *p = strtok(s," ,?!."); p != NULL; p = strtok(NULL, " ,?!.")) {
      puts(p);
    }
    return 0;
}

Tín dụng thuộc về Anders K.


0

bạn có thể quét mảng char để tìm mã thông báo nếu bạn tìm thấy nó, chỉ cần in dòng mới còn lại hãy in char.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    char *s;
    s = malloc(1024 * sizeof(char));
    scanf("%[^\n]", s);
    s = realloc(s, strlen(s) + 1);
    int len = strlen(s);
    char delim =' ';
    for(int i = 0; i < len; i++) {
        if(s[i] == delim) {
            printf("\n");
        }
        else {
            printf("%c", s[i]);
        }
    }
    free(s);
    return 0;
}

0

Vì vậy, đây là một đoạn mã để giúp hiểu rõ hơn về chủ đề này.

In mã thông báo

Nhiệm vụ: Cho một câu, s, in từng từ của câu đó vào một dòng mới.

char *s;
s = malloc(1024 * sizeof(char));
scanf("%[^\n]", s);
s = realloc(s, strlen(s) + 1);
//logic to print the tokens of the sentence.
for (char *p = strtok(s," "); p != NULL; p = strtok(NULL, " "))
{
    printf("%s\n",p);
}

Đầu vào: How is that

Kết quả:

How
is
that

Giải thích: Vì vậy, ở đây, hàm "strtok ()" được sử dụng và nó được lặp lại bằng cách sử dụng vòng lặp for để in các mã thông báo trong các dòng riêng biệt.

Hàm sẽ nhận các tham số là 'chuỗi' và 'điểm ngắt' và ngắt chuỗi tại các điểm ngắt đó và hình thành mã thông báo. Bây giờ, những mã thông báo đó được lưu trữ trong 'p' và được sử dụng thêm để in.


tôi nghĩ rằng giải thích thông qua một ví dụ tốt hơn nhiều so với việc tham khảo một số tài liệu.
tr_abhishek
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.