【问题标题】:How to store chars into an array using pointers and malloc?如何使用指针和 malloc 将字符存储到数组中?
【发布时间】:2019-01-22 12:28:53
【问题描述】:

我现在的问题是我为不同的单词占用了空间,但是我在将其存储为数组时遇到了问题。即使有一些类似的帖子,但似乎对我没有任何帮助,我完全被困在这里。我想保持这种格式(我不想改变函数的定义)。感谢所有帮助和cmets!

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



int i, len = 0, counter = 0;
char ** p = 0;

for(i = 0; s[i] != '\0'; i++){
    len++;

    if(s[i] == ' ' || s[i+1] == '\0'){

        counter ++;
 for(i = 0; i < len; i++){
    p[i] = s[i];
    }

}
printf("%d\n", len);
printf("%d\n", counter);
return p; 
}

int main() {
char *s = "This is a string";
int n;
int i;



for(i = 0; i < n*; i++){
 //also not sure how to print this 
}
}

【问题讨论】:

  • 您可以将p 视为一个指针数组。因此,例如p[i] 是一个指针,而 p[i] = s[i] 是错误的,因为 s[i] 不是一个指针。此外,您不应该对ps 使用相同的索引,因为在p 中分配的下一个元素很可能与s 中的当前字符的索引不同。并且不要忘记 C 中的 char 字符串实际上称为 null-terminated 字节字符串,所以除非您想修改原始字符串(如 strtok 确实) 那么你需要复制部分源代码
  • 您当前的代码还有许多其他问题,例如您多次分配内存并重新分配给p,从而丢失了先前分配的内存。我建议你坐下来拿一张纸,写下你的需求,然后详细分析需求,找出可以转化为工作实现的设计。
  • 您的代码有拼写错误,无法编译。尝试用-Wall -Werror -Wpedantic -Wsign-conversion -Wshadow -Wconversion -Wunintialized -Wextra编译
  • 你正在传递 n 并且从不使用 n

标签: c arrays function pointers


【解决方案1】:

我编辑了您的代码,现在可以正常工作了:

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

char** split(const char* s, int *n);
char** split(const char* s, int *n) {
    int i, len = 0, counter = 0;
    char ** p = 0;
    for(int i = 0; ; ++i) {
        if(s[i] == '\0') {
            break;
        }
        if(s[i] == ' ') {
            counter += 1;
        }
    }
    ++counter;

    p = (char **) malloc(counter * sizeof(char*));

    for(int i = 0, c = 0; ; ++i, ++c) {
        if(s[i] == '\0') {
            break;
        }
        len = 0;
        while(s[len + i + 1] != ' ' && s[len + i + 1] != '\0') {
            ++len;
        }
        p[c] = (char *) malloc(len * sizeof(char) + 1);
        int k = 0;
        for(int j = i; j < i + len + 1; ++j) {
            p[c][k++] = s[j];
        }
        p[c][k] = '\0';
        i += len + 1;
    }
    *n = counter;
    return p;
}

int main() {
    char *s = "This is a string";
    int n;
    int i;
    char** split_s = split(s, &n);

    for(i = 0; i < n; i++) {
        printf("%s\n", split_s[i]);
    }
}

但我建议你做一些清理工作。

【讨论】:

    【解决方案2】:

    这是一个使用 sscanf 的解决方案。 scanf 和 sscanf 将空间视为输入的结束。我利用这一点让它为你工作。

    char *str = (char*) "This is a string";
    char buffer[50];
    char ** p = (char**)malloc(1 * sizeof(*p));
    for (int i = 0; str[0] != NULL; i++)
    {
        if (i > 0)
        {
            p = (char**)realloc(p, i * sizeof(p));
        }
    
        sscanf(str, "%s", buffer);
        int read = strlen(buffer);
        str += read + 1;                
    
        p[i] = (char*)malloc(sizeof(char)*read + 1);
        strcpy(p[i], buffer);
        printf("%s\n", p[i]);
    }
    

    由于这个指针在两个维度上都在增长,每次找到一个新字符串时,我们都需要调整 p 本身的大小,然后它包含的新地址也应该调整大小。

    【讨论】:

      【解决方案3】:

      我现在的问题是我使用 malloc 为不同的单词占用了空间,但是我在将其存储为数组时遇到了问题。

      当字符串集合需要可寻址内存时,指针集合以及每个指针所需的内存。

      在您的代码中:

      p = (char**)malloc(counter*sizeof(char*));
      

      您已创建指针集合,但尚未在这些位置创建内存以容纳字符串。 (顺便说一句,演员表是不必要的)

      以下是创建指针集合和每个指针的内存的基本步骤:

      //for illustration, pick sizes for count of strings needed,
      //and length of longest string needed.
      #define NUM_STRINGS 5
      #define STR_LEN     80
      
      char **stringArray = NULL;
      
      stringArray = malloc(NUM_STRINGS*sizeof(char *));// create collection of pointers
      if(stringArray)
      {
          for(int i=0;i<NUM_STRINGS;i++)
          {
              stringArray[i] = malloc(STR_LEN + 1);//create memory for each string
              if(!stringArray[i])                  //+1 room for nul terminator
              {
                  //handle error
              }
          }
      }
      

      作为一个函数,它可能看起来像这样:(用 calloc 替换 malloc 以获得初始化空间)

      char ** Create2DStr(size_t numStrings, size_t maxStrLen)
      {
          int i;
          char **a = {0};
          a = calloc(numStrings, sizeof(char *));
          for(i=0;i<numStrings; i++)
          {
            a[i] = calloc(maxStrLen + 1, 1);
          }
          return a;
      }
      

      在您的 split() 函数中使用它:

      char** split(const char* s, int *n){
      
          int i, len = 0, counter = 0, lenLongest = 0
          char ** p = 0;
      
          //code to count words and longest word
      
          p = Create2DStr(counter, longest + 1); //+1 for nul termination
          if(p)
          {
              //your searching code
              //...
              // when finished, free memory
      

      【讨论】:

        【解决方案4】:

        让我们从逻辑开始。

        如何处理像A quick brown fox. 这样的字符串?我建议:

        1. 计算字数以及存储字所需的内存量。 (在 C 中,每个字符串都以一个终止的 nul 字节结束,\0。)

        2. 为指针和单词分配足够的内存。

        3. 从源字符串中复制每个单词。

        我们有一个字符串作为输入,我们想要一个字符串数组作为输出。最简单的选择是

        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(&amp;words, i) 获取指向ith 单词的指针,或者如果ith 单词尚不存在则使用指向空字符串的指针,并使用wordarray_word_len(&amp;words, i) 获取该单词的长度(比调用strlen(wordarray_word_ptr(&amp;words, i)) 快得多)。

        我们不能在这里使用char * 的根本原因是realloc()ing 数据区(字指针指向的地方)可能会改变它的地址。如果发生这种情况,我们必须调整数组中的每个指针。相反,使用数据区域的偏移量要容易得多。

        这种方法的唯一缺点是删除单词并不意味着数据区域的相应缩小。但是,可以编写一个简单的“压缩器”函数,将数据重新打包到一个新区域,从而将删除单词留下的空洞“移动”到数据区域的末尾。通常,这不是必需的,但您可能希望在 wordarray 结构中添加一个成员,例如从单词删除中丢失的字符数,以便下次数据区域调整大小时可以启发式地进行压缩.

        【讨论】:

        • 哇。感谢您的精彩解释,它真的很有帮助,我现在非常了解!
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-02-13
        • 2018-02-10
        • 2014-04-12
        • 1970-01-01
        相关资源
        最近更新 更多