从分隔字符串中创建字符串数组

5

如何在C语言中高效地将一个分隔符字符串转换为字符串数组(不是C++)?例如,我可能有以下字符串:

char *input = "valgrind --leak-check=yes --track-origins=yes ./a.out"

源字符串中分隔符始终只有一个空格。我希望得到一个由malloc分配的字符串数组char *myarray[],使得:

myarray[0]=="valgrind"
myarray[1]=="--leak-check=yes"
...

编辑 我必须假设inputString中有任意数量的标记,所以我不能仅限于10个或其他数量。

我尝试使用strtok和我实现的链表进行混乱的解决方案,但valgrind抱怨太多了,所以我放弃了。

(如果你想知道,这是我正在尝试编写的基本Unix shell.)


@Sneesh:这是一个很好的例子,展示了在C语言中实现的多种方法,正如古老的格言所说:“杀猫有多种方法……”我给你点赞。 - t0mm13b
5个回答

2

关于这样的东西:

char* string = "valgrind --leak-check=yes --track-origins=yes ./a.out";
char** args = (char**)malloc(MAX_ARGS*sizeof(char*));
memset(args, 0, sizeof(char*)*MAX_ARGS);

char* curToken = strtok(string, " \t");

for (int i = 0; curToken != NULL; ++i)
{
  args[i] = strdup(curToken);
  curToken = strtok(NULL, " \t");
}

其实我认为使用一个256个指向字符串的缓冲区并不会浪费太多,除非你真的需要保留内存。 - Jack
strtok() 修改了输入字符串,因此在某些平台上对字符串字面值使用它会导致崩溃。 - bk1e
我可以假设MAX_ARGS是安全的,比如10,000,但代码仍应该适用于10,001个参数... - yavoh
好的,那么唯一的区别就是使用链表了...你对此有什么问题吗?你只需要在列表元素中使用strdup()函数。 - Jack
将MAX_ARGS不作为常量而在运行时确定它将很容易。可以遍历输入并计算空格数,或调用strlen()并假设最坏情况是每个字符都是空格。 - jamesdlin
显示剩余5条评论

2

如果你一开始就拥有了所有的输入内容input,那么你所拥有的令牌数永远不会超过strlen(input)。如果你不允许空字符串作为一个令牌,那么你最多只能拥有strlen(input)/2个令牌。所以除非input的规模很大,否则您可以安全地编写代码。

char ** myarray = malloc( (strlen(input)/2) * sizeof(char*) );

int NumActualTokens = 0;
while (char * pToken = get_token_copy(input))
{ 
   myarray[++NumActualTokens] = pToken;
   input = skip_token(input);
}

char ** myarray = (char**) realloc(myarray, NumActualTokens * sizeof(char*));

作为更进一步的优化,你可以保留 input,只需用 \0 替换空格,并将指向 input 缓冲区的指针放入 myarray[] 中。除非你有某种原因需要单独释放它们,否则不需要为每个标记分配单独的 malloc。

使用您的 strlen(input)/2 的想法- 谢谢! - yavoh

1
在OSX上的strsep(3)手册中:
   char **ap, *argv[10], *inputstring;

   for (ap = argv; (*ap = strsep(&inputstring, " \t")) != NULL;)
           if (**ap != '\0')
                   if (++ap >= &argv[10])
                           break;

编辑为任意数量的标记:

char **ap, **argv, *inputstring;

int arglen = 10;
argv = calloc(arglen, sizeof(char*));
for (ap = argv; (*ap = strsep(&inputstring, " \t")) != NULL;)
    if (**ap != '\0')
        if (++ap >= &argv[arglen])
        {
            arglen += 10;
            argv = realloc(argv, arglen);
            ap = &argv[arglen-10];
        }

或者类似于那样。上述方法可能不起作用,但如果不行也不会差太远。构建一个链表比不断调用realloc更有效率,但这真的不是重点——重点是如何最好地利用strsep


谢谢。我忘了提到,我必须假设inputString中有任意数量的标记-我不能假定为10个,比如。 - yavoh

1

你记得为字符串结尾的终止空字符分配额外的一个字节吗?


是的:char *singleToken = (char *)malloc(strlen(tokPtr)*sizeof(char)+1);,其中tokPtrstrtok的返回值。 - yavoh

0

看了其他答案,对于 C 语言的初学者来说,由于代码紧凑,可能会难以理解。我认为应该为初学者提供一个更简单的方法,即直接解析字符串,而不是使用 strtok...就像这样:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
char **parseInput(const char *str, int *nLen); void resizeptr(char ***, int nLen);
int main(int argc, char **argv){ int maxLen = 0; int i = 0; char **ptr = NULL; char *str = "valgrind --leak-check=yes --track-origins=yes ./a.out"; ptr = parseInput(str, &maxLen); if (!ptr) printf("Error!\n"); else{ for (i = 0; i < maxLen; i++) printf("%s\n", ptr[i]); } for (i = 0; i < maxLen; i++) free(ptr[i]); free(ptr); return 0; }
char **parseInput(const char *str, int *Index){ char **pStr = NULL; char *ptr = (char *)str; int charPos = 0, indx = 0; while (ptr++ && *ptr){ if (!isspace(*ptr) && *ptr) charPos++; else{ resizeptr(&ptr, ++indx); pStr[indx-1] = (char *)malloc(((charPos+1) * sizeof(char))+1); if (!pStr[indx-1]) return NULL; strncpy(pStr[indx-1], ptr - (charPos+1), charPos+1); pStr[indx-1][charPos+1]='\0'; charPos = 0; } } if (charPos > 0){ resizeptr(&pStr, ++indx); pStr[indx-1] = (char *)malloc(((charPos+1) * sizeof(char))+1); if (!pStr[indx-1]) return NULL; strncpy(pStr[indx-1], ptr - (charPos+1), charPos+1); pStr[indx-1][charPos+1]='\0'; } *Index = indx; return (char **)pStr; }
void resizeptr(char ***ptr, int nLen){ if (*(ptr) == (char **)NULL){ *(ptr) = (char **)malloc(nLen * sizeof(char*)); if (!*(ptr)) perror("error!"); }else{ char **tmp = (char **)realloc(*(ptr),nLen); if (!tmp) perror("error!"); *(ptr) = tmp; } }
我稍微修改了代码,使它更容易理解。我只使用了一个字符串函数strncpy。虽然这个函数有点冗长,但它可以动态地重新分配字符串数组的大小,而不是使用硬编码的MAX_ARGS。这意味着当只需要3或4个指针时,双指针已经占用了内存,使用realloc可以使内存使用更加高效和小巧。简单的解析可以通过使用isspace来完成,它会迭代使用指针。当遇到空格时,它会realloc双指针,并malloc偏移量以保存字符串。
请注意,在resizeptr函数中如何使用三级指针。实际上,我认为这是一个简单的C程序、指针、realloc、malloc、按引用传递、解析字符串的基本元素的绝佳示例...
希望这能帮到你, 最好的祝愿, 汤姆。

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