Як strtok () розділяє рядок на лексеми на C?


114

Будь ласка, поясніть мені strtok()функціонування функції. Посібник говорить, що він розбиває рядок на лексеми. Я не можу зрозуміти з посібника, що він насправді робить.

Я додав годинник strі *pchперевіряв його роботу, коли відбувся перший цикл, але вміст strбув лише "цим". Як друкувались на екрані вихідні дані?

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

Вихід:

Розбиття рядка "- Це, зразок рядка." в жетони:
Це
а
зразок
рядок

5
strtok()змінює свій рядок аргументу, припиняючи лексеми NUL перед поверненням. Якщо ви спробуєте вивчити весь буфер (str []), ви побачите, що він змінюється між послідовними викликами на strtok().
Майкл Фукаракіс

Замість того , щоб дивитися str, дивитися str[0], str[1], str[2], ...
PMG

@pmg: Я переглядав str [0] і str [1] .str [1], слід мати "\ 0", але це був пробіл.
фуддін

3
Чесно кажучи, я ніколи не намагався перевірити, але я думаю, що він зберігає останній переданий вказівник, а також положення, в якому він залишився. Тоді він може просто продовжуватися, якщо вказівник NULL, або очистити позицію і почати спочатку, якщо ні.
chris

Відповіді:


38

strtok()ділить рядок на лексеми. тобто, починаючи з будь-якого роздільника до наступного, це буде ваш один маркер. У вашому випадку початковий маркер буде з "-" і закінчується наступним пробілом "". Тоді наступний маркер почнеться з "" і закінчиться з ",". Тут ви отримуєте "Це" як вихід. Аналогічно, решта рядка розбивається на маркери з простору в простір і, нарешті, закінчується останній маркер "."


умова закінчення для одного лексеми стає початковим символом наступного лексеми - чи є нульовий символ, розміщений на місці закінчуваної умови?
фуддін

1
@ fahad- Так, всі наявні вами деліметри будуть замінені символом NUL, як це також запропонували інші люди.
Sachin Shanbhag

Якщо всі роздільники замінені на Nul, чому тоді рядок містить "-th"? Він повинен містити "\ 0"
футбодін

2
@fahad - Він замінює лише розділові знаки на NUL, а не всі символи між роздільниками. Його вид розщеплення рядка на кілька лексем. Ви отримуєте "Це", тому що це між двома вказаними роздільниками, а не "-ці".
Sachin Shanbhag

1
@Fahad - Так, абсолютно. Наскільки я розумію, усі пробіли "," і "-" замінені на NUL, оскільки ви вказали їх як роздільники.
Sachin Shanbhag

212

функція виконання strtok працює так

під час першого виклику strtok ви надаєте рядок, який ви хочете маркірувати

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

у наведеному вище рядку рядка, здається, є хорошим розмежувачем між словами, тому дозволяємо використовувати таке:

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

що відбувається зараз, це те, що 's' шукається, поки не знайдеться пробільний символ, повернеться перший маркер ('цей') і p вказує на цей маркер (рядок)

щоб отримати наступний маркер і продовжити той самий рядок, NULL передається як перший аргумент, оскільки strtok підтримує статичний покажчик на попередню передану рядок:

p = strtok(NULL," ");

p тепер вказує на "є"

і так далі, поки не знайдеться більше пробілів, тоді остання рядок повертається як остання лексема 'рядок'.

зручніше ви можете написати це так, замість цього, щоб надрукувати всі жетони:

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

Редагувати:

Якщо ви хочете зберегти повернені значення, strtokвам потрібно скопіювати маркер в інший буфер, наприклад, strdup(p);оскільки початковий рядок (на який вказує статичний покажчик всередині strtok) змінюється між ітераціями, щоб повернути маркер.


Тож насправді він не розміщує нульовий символ між рядком? Чому мій годинник показує, що в рядку залишилося лише "ЦЕ"?
фуддін

4
він дійсно замінює '', що було знайдено, на '\ 0'. І пізніше це не відновлюється ", тож ваша струна зруйнована назавжди.

33
+1 для статичного буфера, це те, що я не зрозумів
IEatBagels

1
Дуже важлива деталь, відсутня у рядку "повертається перший маркер і pвказує на цей маркер" , полягає в тому, що strtokпотрібно мутувати вихідний рядок, розміщуючи нульові символи замість роздільника (інакше інші функції рядка не знають, де лексема закінчується). І він також відстежує стан, використовуючи статичну змінну.
Гроо

@Groo Я думаю, я вже додав, що в редагуванні, яке я робив у 2017 році, але ви маєте рацію.
AndersK

25

strtokпідтримує статичну, внутрішню посилання, що вказує на наступний доступний маркер у рядку; якщо ви передасте йому покажчик NULL, він буде працювати з цього внутрішнього посилання.

Це причина strtokне повторного вступу; як тільки ви передаєте йому новий вказівник, ця стара внутрішня посилання стає клоброваною.


Що ви маєте на увазі під старим внутрішнім посиланням «ставати клоброваним». Ви маєте на увазі «переписати»?
ylun.ca

1
@ ylun.ca: так, це я маю на увазі.
Джон Боде

10

strtokне змінює сам параметр ( str). Він зберігає цей покажчик (у локальній статичній змінній). Потім він може змінити те, на що вказує цей параметр у наступних дзвінках, не повертаючи параметр назад. (І він може просунути той вказівник, який він зберег, однак йому потрібно виконувати свої операції.)

На strtokсторінці POSIX :

Ця функція використовує статичне сховище для відстеження поточного положення рядка між дзвінками.

Є безпечний для потоків варіант ( strtok_r), який не робить цього типу магії.


2
Ну а функції бібліотеки С датуються назад, коли введення різьби взагалі не було на малюнку (яке почало існувати лише в 2011 році, що стосується стандарту С), тому повторне вступ не було дуже важливим ( Я вважаю). Цей статичний локальний робить функцію "простою у використанні" (для деякого визначення "простою"). Як і ctimeповернення статичного рядка - практичне (нікому не потрібно дивуватися, хто повинен його звільнити), але не повторно вступає і не відправляє вас, якщо ви цього не знаєте.
Мат

Це неправильно: " strtokне змінює сам параметр ( str)." puts(str);друкує "- Це" з моменту strtokзміни str.
MarredCheese

1
@MarredCheese: прочитайте ще раз. Він не змінює покажчик. Він змінює дані, на які вказує вказівник (тобто рядкові дані)
Мат.

Ну гаразд, я не розумів, що саме до цього ти потрапляєш. Домовились.
MarredCheese

8

Перший раз, коли ви його зателефонуєте, ви надаєте рядок, для якого ви хочете ознайомитися strtok. А потім, щоб отримати наступні лексеми, ви просто надаєте NULLцій функції, поки вона повертає не NULLпокажчик.

strtokФункція записує рядок , яку ви вказали при першому виклику його. (Що насправді небезпечно для багатопотокових програм)


8

strtok буде токенізувати рядок, тобто перетворить його в ряд підрядків.

Це робиться шляхом пошуку роздільників, які розділяють ці лексеми (або підрядки). І ви вказуєте роздільники. У вашому випадку ви хочете "" або "," або "." або "-" бути роздільником.

Модель програмування для вилучення цих маркерів полягає в тому, що ви передаєте strtok вашій основній рядку та набору роздільників. Потім ви викликаєте це повторно, і кожен раз strtok повертає наступний маркер, який він знайде. Поки вона не повернеться до нуля, коли вона поверне нуль. Інше правило полягає в тому, що ви передаєте рядок лише в перший раз, а NULL - для наступних разів. Це спосіб сказати strtok, якщо ви починаєте новий сеанс токенізації з новою рядком або ви отримуєте жетони з попереднього сеансу токенізації. Зауважте, що strtok запам'ятовує свій стан для сеансу токенізації. І тому це не є ретентом або безпечним для потоків (замість цього слід використовувати strtok_r). Інша річ, яку потрібно знати, це те, що вона фактично змінює початковий рядок. Він пише "\ 0" для розмежувачів, які вони знаходять.

Один із способів викликати strtok, таким чином, полягає в наступному:

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

Результат:

this
is
the
string
I
want
to
parse

5

strtok змінює вхідний рядок. Він розміщує в ньому нульові символи ('\ 0'), щоб він повертав біти початкової рядки як лексеми. Насправді strtok не виділяє пам'ять. Ви можете зрозуміти це краще, якщо ви намалюєте рядок як послідовність коробок.


3

Щоб зрозуміти, як strtok()працює, спочатку потрібно знати, що таке статична змінна . Це посилання пояснює це досить добре ....

Ключовим фактором операції strtok()є збереження місця розташування останнього сепаратора між сесійними викликами (саме тому strtok()продовжується розбір тієї самої оригінальної рядки, яка передається їй, коли вона викликається null pointerпослідовними дзвінками).

Погляньте на мою власну strtok()реалізацію, яка називається zStrtok(), яка має дещо інший функціонал, ніж той, який надає компаніяstrtok()

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

І ось приклад використання

  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)

Код з бібліотеки обробки рядків, яку я підтримую в Github , називається zString. Подивіться на код або навіть внесіть внесок :) https://github.com/fnoyanisi/zString


3

Ось як я реалізував strtok, Не так вже й чудово, але попрацювавши на ньому 2 години, нарешті, він запрацював. Він підтримує декілька роздільників.

#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

Ось моя реалізація, яка використовує хеш-таблицю для роздільника, це означає, що це O (n) замість O (n ^ 2) (ось посилання на код) :

#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 () зберігає вказівник у статичній змінній, де ви востаннє зупинялися, тому на другому виклику, коли ми передаємо null, strtok () отримує вказівник від статичної змінної.

Якщо ви вкажете одне і те ж ім'я рядка, воно знову починається з початку.

Більше того, strtok () є руйнівним, тобто вносить зміни в початковий рядок. тому переконайтеся, що у вас завжди є копія оригіналу.

Ще однією проблемою використання strtok () є те, що оскільки він зберігає адресу в статичних змінних, в багатопотоковому програмуванні виклик strtok () не один раз спричинить помилку. Для цього використовуйте strtok_r ().


0

Для тих, хто ще не встигає зрозуміти цю strtok()функцію, погляньте на цей приклад пітонту , це чудовий інструмент для візуалізації вашого C (або C ++, Python ...) коду.

Якщо посилання розірвано, вставте:

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

Кредити йдуть до Андерса К.


0

ви можете сканувати масив 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

Отже, це фрагмент коду, який допоможе краще зрозуміти цю тему.

Друк жетонів

Завдання: Давши речення, с, надрукуйте кожне слово речення у новому рядку.

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

Вхід: How is that

Результат:

How
is
that

Пояснення: Отже, тут використовується функція "strtok ()", яка повторюється за допомогою циклу для друку жетонів в окремих рядках.

Функція буде приймати параметри як "string" і "point-break" і розбивати рядок у цих точках зламу та утворювати лексеми. Тепер ці жетони зберігаються в "p" і використовуються далі для друку.


Я думаю, що пояснення на прикладі набагато краще, ніж посилання на якогось документа.
tr_abhishek
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.