[英]How does strtok() split the string into tokens in C?
請向我解釋strtok()
函數的工作strtok()
。 手冊說它將字符串分解為標記。 我無法從手冊中理解它的實際作用。
我在str
和*pch
上添加了*pch
以檢查它在第一個 while 循環發生時的工作情況, str
的內容只是“this”。 下面顯示的輸出是如何打印在屏幕上的?
/* 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;
}
輸出:
Splitting string "- This, a sample string." into tokens: This a sample string
strtok 運行時函數是這樣工作的
第一次調用 strtok 時,您提供了一個要標記的字符串
char s[] = "this is a string";
在上面的字符串空間中似乎是單詞之間的一個很好的分隔符,所以讓我們使用它:
char* p = strtok(s, " ");
現在發生的是搜索 's' 直到找到空格字符,返回第一個標記('this')並且 p 指向該標記(字符串)
為了獲得下一個標記並繼續使用相同的字符串 NULL 作為第一個參數傳遞,因為 strtok 維護一個指向您之前傳遞的字符串的靜態指針:
p = strtok(NULL," ");
p 現在指向“是”
依此類推,直到找不到更多空格,然后最后一個字符串作為最后一個標記“字符串”返回。
更方便的是,您可以這樣寫,而不是打印出所有標記:
for (char *p = strtok(s," "); p != NULL; p = strtok(NULL, " "))
{
puts(p);
}
編輯:
如果你想存儲從strtok
返回的值,你需要將令牌復制到另一個緩沖區,例如strdup(p);
因為原始字符串(由strtok
的靜態指針指向)在迭代之間被修改以返回令牌。
strtok()
將字符串划分為標記。 即從任何一個分隔符開始到下一個將是您的一個標記。 在您的情況下,起始標記將來自“-”並以下一個空格“”結束。 然后下一個標記將從“”開始並以“,”結束。 在這里,您將獲得“This”作為輸出。 類似地,字符串的其余部分從一個空格到另一個空格拆分為標記,最后以“。”結束最后一個標記。
strtok
維護一個指向字符串中下一個可用標記的靜態內部引用; 如果您傳遞給它一個 NULL 指針,它將從該內部引用工作。
這就是strtok
不可重入的原因; 一旦你傳遞一個新的指針,舊的內部引用就會被破壞。
strtok
不會更改參數本身 ( str
)。 它存儲該指針(在局部靜態變量中)。 然后,它可以在后續調用中更改該參數指向的內容,而無需將參數傳回。 (並且它可以推進它保留的指針,但它需要執行其操作。)
從 POSIX strtok
頁面:
此函數使用靜態存儲來跟蹤調用之間的當前字符串位置。
有一個線程安全的變體 ( strtok_r
) 不會做這種魔法。
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
第一次調用它時,您提供要標記化的字符串strtok
。 然后,要獲得以下標記,您只需為該函數提供NULL
,只要它返回一個非NULL
指針。
strtok
函數記錄您在調用它時首先提供的字符串。 (這對於多線程應用程序來說真的很危險)
strtok 修改其輸入字符串。 它在其中放置空字符 ('\\0'),以便將原始字符串的位作為標記返回。 實際上 strtok 不分配內存。 如果將字符串繪制為一系列框,您可能會更好地理解它。
要了解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
這就是我實現 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;
}
對於那些仍然很難理解這個strtok()
函數的人,看看這個pythontutor 示例,它是一個很好的工具,可以可視化你的 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;
}
學分歸Anders K.
這是我的實現,它使用哈希表作為分隔符,這意味着它是 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;
}
strtok 用 NULL 替換第二個參數中的字符,NULL 字符也是字符串的結尾。
strtok() 將指針存儲在您上次離開的靜態變量中,因此在第二次調用時,當我們傳遞 null 時,strtok() 從靜態變量中獲取指針。
如果您提供相同的字符串 name ,它將再次從頭開始。
此外, strtok() 是破壞性的,即它會更改原始字符串。 所以請確保您始終擁有一份原始副本。
使用 strtok() 的另一個問題是,由於它將地址存儲在靜態變量中,因此在多線程編程中多次調用 strtok() 會導致錯誤。 為此使用 strtok_r()。
下面是strtok如何在C中工作的鏈接,下面是Dipak在C ++中解釋的相同程序
如果您發現它只是打印新行,則可以掃描字符數組以查找令牌,否則打印字符。
#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;
}
所以,這是一個有助於更好地理解這個主題的代碼片段。
打印令牌
任務:給定一個句子 s,在一個新行中打印句子中的每個單詞。
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()”函數,並使用 for 循環對其進行迭代以在單獨的行中打印標記。
該函數將參數作為“字符串”和“斷點”,並在這些斷點處斷開字符串並形成標記。 現在,這些標記存儲在“p”中並進一步用於打印。
strtok
正在用給定字符串中的'\\0'
NULL 字符替換分隔符代碼
#include<iostream>
#include<cstring>
int main()
{
char s[]="30/4/2021";
std::cout<<(void*)s<<"\n"; // 0x70fdf0
char *p1=(char*)0x70fdf0;
std::cout<<p1<<"\n";
char *p2=strtok(s,"/");
std::cout<<(void*)p2<<"\n";
std::cout<<p2<<"\n";
char *p3=(char*)0x70fdf0;
std::cout<<p3<<"\n";
for(int i=0;i<=9;i++)
{
std::cout<<*p1;
p1++;
}
}
輸出
0x70fdf0 // 1. address of string s
30/4/2021 // 2. print string s through ptr p1
0x70fdf0 // 3. this address is return by strtok to ptr p2
30 // 4. print string which pointed by p2
30 // 5. again assign address of string s to ptr p3 try to print string
30 4/2021 // 6. print characters of string s one by one using loop
在標記字符串之前
我將字符串 s 的地址分配給某個 ptr(p1) 並嘗試通過該 ptr 打印字符串並打印整個字符串。
標記化后
strtok 將字符串 s 的地址返回到 ptr(p2) ,但是當我嘗試通過 ptr 打印字符串時,它只打印“30”,但沒有打印整個字符串。 所以可以肯定strtok is not just returning adress but it is placing '\\0' character where delimiter is present
。
交叉檢查
1.
我再次將字符串 s 的地址分配給某個 ptr (p3) 並嘗試打印它打印“30”的字符串,因為在對字符串進行標記時,在分隔符處使用 '\\0' 進行更新。
2.
請參閱通過循環逐個字符地打印字符串,第一個分隔符被 '\\0' 替換,因此它打印的是空格而不是 ''
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.