我正在尝试在c中创建一个函数,该函数像在Java或许多其他语言中的split函数一样,对字符串进行拆分。 我做了这个
char **split(char * str, char *ch) {
char **array = (char **)malloc((strlen(str)) * sizeof(*array));
int i = 0;
char *token = strtok(str, ch);
while (token != NULL) {
array[i++] = token;
token = strtok(NULL, ch);
}
free(token);
return array;
}
这似乎有效,但并非总是如此,而且不正确。 假设我们以两种不同的方式调用此方法: 第一个工作的人:
int main(){
while(1){
sleep(1);
char h = ':';
char a[] = "test:1234";
char ** result = split(a,&h);
printf("%s\n",result[0]);
printf("%s\n",result[1]);
free(result);
}
}
而第二个在第二个while周期给我一个分割错误:
int main(){
char a[] = "test:1234";
char h = ':';
while(1){
sleep(1);
char ** result = split(a,&h);
printf("%s\n",result[0]);
printf("%s\n",result[1]);
free(result);
}
}
输出:
test
1234
test
Segmentation fault (core dumped)
我认为这是由于strtok函数对字符串索引的操纵,但我无法理解如何解决它以及为什么它会给我带来分段错误。
答案 0 :(得分:2)
一个问题是您打错了strtok
。
strtok
需要两个字符串,即要拆分的字符串和一串定界符。
但是您没有传递定界符的字符串,而是传递了指向单个字符的指针。
因此将其更改为:
char h = ':'; ---> char *h = ":";
和
char ** result = split(a,&h); ---> char ** result = split(a,h);
您的代码的另一个问题是您希望它始终返回至少两个有效令牌。这是一个错误的假设,它将在第二个代码示例的第二个循环中失败。
在第一个循环中,a
将更改为字符串“ test”,因为strtok
用字符串终止符替换了':'
。
因此,在第二个循环中,将只有一个令牌。这意味着result[1]
没有指向有效的令牌,因此,不允许您打印其指向的内容。
解决此问题的一种方法是在函数中将所有result
指针设置为NULL,例如通过使用calloc
代替malloc
,例如:
char **array = calloc(strlen(str), sizeof(*array));
然后像这样打印:
if (result[0]) printf("%s\n",result[0]);
if (result[1]) printf("%s\n",result[1]);
或更好:
int i = 0;
while(result[i])
{
printf("%s\n",result[i]);
++i;
}
将它们放在一起:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
char **split(char * str, char *ch) {
char **array = calloc(strlen(str), sizeof(*array)); // Use calloc to set
// all pointers to NULL
int i = 0;
char *token = strtok(str, ch);
while (token != NULL) {
array[i++] = token;
token = strtok(NULL, ch);
}
return array;
}
int main(){
char a[] = "test:1234";
char *h = ":";
int z = 0;
while(z < 5){ // Just loop 5 times
//sleep(1);
char ** result = split(a,h);
int i = 0;
while(result[i]) // Print all tokens, i.e. stop when a pointer is NULL
{
printf("%s\n",result[i]);
++i;
}
free(result);
++z;
}
}
输出:
test
1234
test
test
test
test
顺便说一句:
此
free(token);
与
相同free(NULL);
它什么也没做,因此只需删除该行即可。
答案 1 :(得分:1)
strtok使用起来有点棘手,因为它以不同的方式处理内存 从以前的习惯-它修改作为参数传递的字符串,并返回一个指向子字符串的指针,当strtok(NULL,..)返回一个新的指针到缓冲区时,如果缓冲区超出范围,则指针变为无效,或者如果另一个线程正在调用strtok,则指针将变为无效,因此最好在继续操作之前将返回的令牌复制到另一个缓冲区之前。
这可以通过分配一个内存块然后复制来实现 那里的返回值
char **split(char * str, char *ch) {
char **array = (char **)malloc((strlen(str)) * sizeof(*array));
int i = 0;
char *token = strtok(str, ch);
while (token != NULL) {
char* dupToken = malloc(strlen(token)+1);
strcpy(dupToken, token);
array[i++] = dupToken;
token = strtok(NULL, ch);
}
// free(token); // this here is wrong
return array;
}
代码的另一个问题是调用者无法知道 返回数组中有多少个代码,所以我建议另一种方法
一旦您点击了最后一个标记,在返回之前将下一个指针设置为NULL 数组
char **split(char * str, char *ch) {
char **array = (char **)malloc((strlen(str)) * sizeof(*array));
int i = 0;
char *token = strtok(str, ch);
while (token != NULL) {
char* dupToken = malloc(strlen(token)+1);
strcpy(dupToken, token);
array[i++] = dupToken;
token = strtok(NULL, ch);
}
array[i] = NULL;
return array;
}
那样,当您遍历令牌时,您只需检查指针即可
for (int i = 0; array[i] != NULL; ++i)
{
...
}
编辑:然后将另一个条目添加到数组中可能是一件好事,这样您就可以处理最大令牌数+ 1
char **array = (char **)malloc((strlen(str) + 1) * sizeof(*array));
编辑:更改了我对返回的指针会发生什么事的草率描述,只要传递给strtok的原始缓冲区有效,它就是有效的。