C语言中的strtok()函数可以将字符串分割成多个子串,但不会改变原有数据。该函数通常用于解析文本数据。

6

I have the following code:

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

int main (void) {
    char str[] = "John|Doe|Melbourne|6270|AU";

    char fname[32], lname[32], city[32], zip[32], country[32];
    char *oldstr = str;

    strcpy(fname, strtok(str, "|"));
    strcpy(lname, strtok(NULL, "|"));
    strcpy(city, strtok(NULL, "|"));
    strcpy(zip, strtok(NULL, "|"));
    strcpy(country, strtok(NULL, "|"));

    printf("Firstname: %s\n", fname);
    printf("Lastname: %s\n", lname);
    printf("City: %s\n", city);
    printf("Zip: %s\n", zip);
    printf("Country: %s\n", country);
    printf("STR: %s\n", str);
    printf("OLDSTR: %s\n", oldstr);

    return 0;
}

执行输出:

$ ./str
Firstname: John
Lastname: Doe
City: Melbourne
Zip: 6270
Country: AU
STR: John
OLDSTR: John

为什么我无法在stroldstr中保留旧数据?我做错了什么,该如何不改变数据或保留它呢?


在我的回答中这里,我写了一个展示strtok()如何工作的代码(它会修改同一地址空间中的字符串),我认为你应该看一下: - Grijesh Chauhan
1
在提出这样的问题之前,我希望你能阅读strtok()函数的源代码或者函数文档。 - PP.
1
这里是strkok()的源代码 - Grijesh Chauhan
在调用 strtok 之前,要么复制 str 的副本,要么不使用 strtok,而是使用一对指针来括起每个标记并复制它,或者使用 strcspnstrspn 的组合来完成相同的操作。使用其他任何方法,您都可以对字符串字面量进行标记化,因为原始字符串不会被修改,但是 strtok 通过将分隔符替换为空字符来修改原始字符串。 - David C. Rankin
5个回答

31

当你使用strtok(NULL, "|")时,strtok()会找到标记并在该位置放置null(用\0替换标记),并修改字符串。

你的str将变成:

char str[] = John0Doe0Melbourne062700AU;
                 
  Str array in memory 
+------------------------------------------------------------------------------------------------+
|'J'|'o'|'h'|'n'|0|'D'|'o'|'e'|0|'M'|'e'|'l'|'b'|'o'|'u'|'r'|'n'|'e'|0|'6'|'2'|'7'|'0'|0|'A'|'U'|0|
+------------------------------------------------------------------------------------------------+
                 ^  replace | with \0  (ASCII value is 0)

考虑这个图表很重要,因为字符'0'和数字0是不同的(在字符串6270中,图表中用'括起来的是字符,在\0中表示数字0)

当您使用%s打印字符串时,它会打印第一个\0之前的字符,即John

为了保持原始字符串不变,您应该先将字符串复制到某个临时变量tempstr中,然后在strtok()中使用该tempstr字符串:

char str[] = "John|Doe|Melbourne|6270|AU";
char* tempstr = calloc(strlen(str)+1, sizeof(char));
strcpy(tempstr, str);

现在将tempstr字符串用于您的代码中,代替原来的字符串str


1
一个编译良好的答案 +1 :) - 0decimal0
2
你可以用简单的 strdup 替换 calloc + strcpy - Marco Bonelli

3

因为 oldstr 只是一个指针,赋值不会创建字符串的新副本。

在将 str 传递给 strtok 之前复制它:

          char *oldstr=malloc(sizeof(str));
          strcpy(oldstr,str);

您的修正版本:

#include <stdio.h>
#include <string.h>
#include<malloc.h>
int main (void) {

   char str[] = "John|Doe|Melbourne|6270|AU";
   char fname[32], lname[32], city[32], zip[32], country[32];
   char *oldstr = malloc(sizeof(str));
   strcpy(oldstr,str);

    ...................
    free(oldstr);
return 0;
}

编辑:

如@CodeClown所提到的,在您的情况下,最好使用strncpy。而且,在事先修复fname等的大小之前,您可以在它们的位置上使用指针,并根据需要分配内存,既不多也不少。这样,您就可以避免写入超出边界的缓冲区......

另一个想法是:将strtok的结果分配给指针*fname*lname等,而不是数组。看到接受的答案后,strtok似乎是设计成这样使用的。

注意:以这种方式,如果您进一步更改了str,那么fnamelname等也会反映出来。因为它们只是指向str数据而不是新的内存块。因此,请使用oldstr进行其他操作。

#include <stdio.h>
#include <string.h>
#include<malloc.h>
int main (void) {

    char str[] = "John|Doe|Melbourne|6270|AU";
    char *fname, *lname, *city, *zip, *country;
    char *oldstr = malloc(sizeof(str));
    strcpy(oldstr,str);
    fname=strtok(str,"|");
    lname=strtok(NULL,"|");
    city=strtok(NULL, "|");
    zip=strtok(NULL, "|");
    country=strtok(NULL, "|");

    printf("Firstname: %s\n", fname);
    printf("Lastname: %s\n", lname);
    printf("City: %s\n", city);
    printf("Zip: %s\n", zip);
    printf("Country: %s\n", country);
    printf("STR: %s\n", str);
    printf("OLDSTR: %s\n", oldstr);
    free(oldstr);
return 0;
}

好的回答!也许可以使用strncpy(),不要使用静态缓冲区,因为有些人或城市的名称超过32个字符。他下一个问题可能会涉及堆栈破坏 :-) - aggsol
1
在一个真正的程序中 - 而不仅仅是为了提出问题而编写的东西 - 当你完成后,不要忘记释放oldstr使用的内存。 - nurdglaw
@Code Clown,是的,栈破坏:P问题已经通过Grijesh Chauhan的回答解决了。 - bsteo
是的,我会执行 free(str)free(oldstr) - bsteo
@pinkpanther 不在这里,但是在我的工作代码中,我的 str 是动态分配的(从网络中获取Curl数据)。 - bsteo

1

strtok需要一个可写的输入字符串,并且它会修改输入字符串。如果你想保留输入字符串,你必须先复制它。

例如:

char str[] = "John|Doe|Melbourne|6270|AU";
char oldstr[32];

strcpy(oldstr, str);  // Use strncpy if you don't know
                      // the size of str

1
最好先获取字符串的长度,然后创建适当的数据,因为人们的姓名和居住城市都不同。 - aggsol

0
下面的for()循环演示了代码仅在一个位置调用strtok()函数。
int separate( char *flds[], int size, char *fullStr ) {
    int count = 0;
    for( char *cp = fullStr; ( cp = strtok( cp, " " ) ) != NULL; cp = NULL ) {
        flds[ count ] = strdup( cp ); // must be free'd later!
        if( ++count == size )
            break;
    }
    return( count );
}

0
你只是复制了指针到字符串,而不是字符串本身。使用strncpy()来创建一个副本。
char *oldstr = str; // just copy of the address not the string itself!

这并不会阻止strtok()改变字符串的内容。 - Eugene Bujak
@EugeneBujak 是的,但只是在副本中。原始文件保持不变。 - aggsol
那么为什么您的回答中没有包括使用strcpy()函数呢? - anti_gone

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接