让我们从逻辑开始。
如何处理像A quick brown fox. 这样的字符串?我建议:
计算字数以及存储字所需的内存量。 (在 C 中,每个字符串都以一个终止的 nul 字节结束,\0。)
为指针和单词分配足够的内存。
从源字符串中复制每个单词。
我们有一个字符串作为输入,我们想要一个字符串数组作为输出。最简单的选择是
char **split_words(const char *source);
如果发生错误,则返回值为NULL,否则返回由NULL 指针终止的指针数组。所有这些都是一次动态分配的,因此在返回值上调用free() 将释放指针及其内容。
让我们根据上面的要点开始实现逻辑。
#include <stdlib.h>
char **split_words(const char *source)
{
size_t num_chars = 0;
size_t num_words = 0;
size_t w = 0;
const char *src;
char **word, *data;
/* Sanity check. */
if (!source)
return NULL; /* split_words(NULL) will return NULL. */
/* Count the number of words in source (num_words),
and the number of chars needed to store
a copy of each word (num_chars). */
src = source;
while (1) {
/* Skip any leading whitespace (not just spaces). */
while (*src == '\t' || *src == '\n' || *src == '\v' ||
*src == '\f' || *src == '\r' || *src == ' ')
src++;
/* No more words? */
if (*src == '\0')
break;
/* We have one more word. Account for the pointer itself,
and the string-terminating nul char. */
num_words++;
num_chars++;
/* Count and skip the characters in this word. */
while (*src != '\0' && *src != '\t' && *src != '\n' &&
*src != '\v' && *src != '\f' && *src != '\r' &&
*src != ' ') {
src++;
num_chars++;
}
}
/* If the string has no words in it, return NULL. */
if (num_chars < 1)
return NULL;
/* Allocate memory for both the pointers and the data.
One extra pointer is needed for the array-terminating
NULL pointer. */
word = malloc((num_words + 1) * sizeof (char *) + num_chars);
if (!word)
return NULL; /* Not enough memory. */
/* Since 'word' is the return value, and we use
num_words + 1 pointers in it, the rest of the memory
we allocated we use for the string contents. */
data = (char *)(word + num_words + 1);
/* Now we must repeat the first loop, exactly,
but also copy the data as we do so. */
src = source;
while (1) {
/* Skip any leading whitespace (not just spaces). */
while (*src == '\t' || *src == '\n' || *src == '\v' ||
*src == '\f' || *src == '\r' || *src == ' ')
src++;
/* No more words? */
if (*src == '\0')
break;
/* We have one more word. Assign the pointer. */
word[w] = data;
w++;
/* Count and skip the characters in this word. */
while (*src != '\0' && *src != '\t' && *src != '\n' &&
*src != '\v' && *src != '\f' && *src != '\r' &&
*src != ' ') {
*(data++) = *(src++);
}
/* Terminate this word. */
*(data++) = '\0';
}
/* Terminate the word array. */
word[w] = NULL;
/* All done! */
return word;
}
我们可以通过一个小测试main()来测试以上内容:
#include <stdio.h>
int main(int argc, char *argv[])
{
char **all;
size_t i;
all = split_words(" foo Bar. BAZ!\tWoohoo\n More");
if (!all) {
fprintf(stderr, "split_words() failed.\n");
exit(EXIT_FAILURE);
}
for (i = 0; all[i] != NULL; i++)
printf("all[%zu] = \"%s\"\n", i, all[i]);
free(all);
return EXIT_SUCCESS;
}
如果我们编译并运行上面的,我们得到
all[0] = "foo"
all[1] = "Bar."
all[2] = "BAZ!"
all[3] = "Woohoo"
all[4] = "More"
这种方法的缺点(使用一个malloc() 调用来为指针和数据分配内存)是我们不能轻易地增大数组;我们真的可以把它当作一大团。
一个更好的方法,尤其是如果我们打算动态添加新词,是使用一个结构:
typedef struct {
size_t max_words; /* Number of pointers allocated */
size_t num_words; /* Number of words in array */
char **word; /* Array of pointers */
} wordarray;
不幸的是,这一次我们需要分别分配每个单词。但是,如果我们使用一个结构来描述公共分配缓冲区中的每个单词,比如说
typedef struct {
size_t offset;
size_t length;
} wordref;
typedef struct {
size_t max_words;
size_t num_words;
wordref *word;
size_t max_data;
size_t num_data;
char *data;
} wordarray;
#define WORDARRAY_INIT { 0, 0, NULL, 0, 0, NULL }
static inline const char *wordarray_word_ptr(wordarray *wa, size_t i)
{
if (wa && i < wa->num_words)
return wa->data + wa->word[i].offset;
else
return "";
}
static inline size_t wordarray_word_len(wordarray *wa, size_t i)
{
if (wa && i < wa->num_words)
return wa->word[i].length;
else
return 0;
}
这个想法是,如果你声明
wordarray words = WORDARRAY_INIT;
您可以使用wordarray_word_ptr(&words, i) 获取指向ith 单词的指针,或者如果ith 单词尚不存在则使用指向空字符串的指针,并使用wordarray_word_len(&words, i) 获取该单词的长度(比调用strlen(wordarray_word_ptr(&words, i)) 快得多)。
我们不能在这里使用char * 的根本原因是realloc()ing 数据区(字指针指向的地方)可能会改变它的地址。如果发生这种情况,我们必须调整数组中的每个指针。相反,使用数据区域的偏移量要容易得多。
这种方法的唯一缺点是删除单词并不意味着数据区域的相应缩小。但是,可以编写一个简单的“压缩器”函数,将数据重新打包到一个新区域,从而将删除单词留下的空洞“移动”到数据区域的末尾。通常,这不是必需的,但您可能希望在 wordarray 结构中添加一个成员,例如从单词删除中丢失的字符数,以便下次数据区域调整大小时可以启发式地进行压缩.