Как сделать динамический размерный массив в C? - PullRequest
13 голосов
/ 28 августа 2011

Я привык к PHP, но я начинаю изучать C. Я пытаюсь создать программу, которая читает файл построчно и сохраняет каждую строку в массив.

Пока чтоУ меня есть программа, которая читает файл построчно и даже печатает каждую строку, как она идет, но теперь мне просто нужно добавить каждую строку в массив.

Мой приятель вчера вечером рассказывал мне немного оЭто.Он сказал, что мне придется использовать многомерный массив в C, поэтому в основном array[x][y].Сама часть [y] проста, потому что я знаю максимальное количество байтов, которое будет в каждой строке.Тем не менее, я не знаю, сколько строк файла будет.чувствуете, что может быть более простой способ сделать это.

Есть идеи или даже подсказка в правильном направлении?Я ценю любую помощь.

Ответы [ 6 ]

11 голосов
/ 28 августа 2011

Для динамического выделения 2D-массива:

char **p;
int i, dim1, dim2;


/* Allocate the first dimension, which is actually a pointer to pointer to char   */
p = malloc (sizeof (char *) * dim1);

/* Then allocate each of the pointers allocated in previous step arrays of pointer to chars
 * within each of these arrays are chars
 */
for (i = 0; i < dim1; i++)
  {
    *(p + i) = malloc (sizeof (char) * dim2);
   /* or p[i] =  malloc (sizeof (char) * dim2); */
  }

 /* Do work */

/* Deallocate the allocated array. Start deallocation from the lowest level.
 * that is in the reverse order of which we did the allocation
 */
for (i = 0; i < dim1; i++)
{
  free (p[i]);
}
free (p);

Изменить вышеуказанный метод. Когда вам нужно добавить еще одну строку, выполните *(p + i) = malloc (sizeof (char) * dim2); и обновите i. В этом случае вам нужно предсказать максимальное количество строк в файле, которое указано в переменной dim1, для которой мы выделяем массив p в первый раз. Это будет выделять только (sizeof (int *) * dim1) байтов, что намного лучше, чем char p[dim1][dim2] (в c99).

Есть другой способ, которым я думаю. Выделяйте массивы в блоки и объединяйте их в цепочки при переполнении.

struct _lines {
   char **line;
   int n;
   struct _lines *next;
} *file;

file = malloc (sizeof (struct _lines));
file->line = malloc (sizeof (char *) * LINE_MAX);
file->n = 0;
head = file;

После этого первый блок готов к использованию. Когда вам нужно вставить строку, просто сделайте:

/* get line into buffer */
file.line[n] = malloc (sizeof (char) * (strlen (buffer) + 1));
n++;

Когда n равен LINE_MAX, выделите другой блок и свяжите его с этим.

struct _lines *temp;

temp = malloc (sizeof (struct _lines));
temp->line = malloc (sizeof (char *) * LINE_MAX);
temp->n = 0;
file->next = temp;
file = file->next;

Примерно так.

Когда один из блоков n становится 0, освободите его и обновите указатель текущего блока file на предыдущий. Вы можете либо пройти от начала одного связанного списка и перейти от начала или использовать двойные ссылки.

7 голосов
/ 28 августа 2011

В C. нет стандартного типа массива с изменяемым размером. Вы должны реализовать его самостоятельно или использовать стороннюю библиотеку.Вот простой простой пример:

typedef struct int_array
{
    int *array;
    size_t length;
    size_t capacity;
} int_array;

void int_array_init(int_array *array)
{
    array->array = NULL;
    array->length = 0;
    array->capacity = 0;
}

void int_array_free(int_array *array)
{
    free(array->array);
    array->array = NULL;
    array->length = 0;
    array->capacity = 0;
}

void int_array_push_back(int_array *array, int value)
{
    if(array->length == array->capacity)
    {
        // Not enough space, reallocate.  Also, watch out for overflow.
        int new_capacity = array->capacity * 2;
        if(new_capacity > array->capacity && new_capacity < SIZE_T_MAX / sizeof(int))
        {
            int *new_array = realloc(array->array, new_capacity * sizeof(int));
            if(new_array != NULL)
            {
               array->array = new_array;
               array->capacity = new_capacity;
            }
            else
                ; // Handle out-of-memory
        }
        else
            ; // Handle overflow error
    }

    // Now that we have space, add the value to the array
    array->array[array->length] = value;
    array->length++;
}

Используйте его так:

int_array a;
int_array_init(&a);

int i;
for(i = 0; i < 10; i++)
    int_array_push_back(&a, i);
for(i = 0; i < a.length; i++)
    printf("a[%d] = %d\n", i, a.array[i]);

int_array_free(&a);

Конечно, это только для массива int с.Поскольку в C нет шаблонов, вам придется либо поместить весь этот код в макрос для каждого другого типа массива (или использовать другой препроцессор, такой как GNU m4 ).Или вы можете использовать универсальный контейнер массива, который использует либо указатели void* (требующие malloc '' для всех элементов массива), либо непрозрачные BLOB-объекты памяти, для которых потребуется приведение с каждым доступом к элементу и memcpy для каждогоэлемент get / set.

В любом случае это не красиво.Двумерные массивы еще страшнее.

1 голос
/ 29 августа 2011

Вы можете использовать функции malloc и realloc для динамического выделения и изменения размера массива указателей до char, и каждый элемент массива будет указывать на строку, считанную из файла (где хранится эта строкатакже выделяется динамически).Для простоты мы предположим, что максимальная длина каждой строки меньше M символов (с учетом новой строки), поэтому нам не нужно выполнять динамическое изменение размеров отдельных строк.

Вам потребуетсяотслеживать размер массива вручную каждый раз, когда вы его расширяете.Обычная техника - удваивать размер массива при каждом расширении, а не на фиксированный размер;это минимизирует количество вызовов до realloc, что потенциально дорого.Конечно, это означает, что вам придется отслеживать две величины;общий размер массива и количество считываемых элементов.

Пример:

#define INITIAL_SIZE ... // some size large enough to cover most cases

char **loadFile(FILE *stream, size_t *linesRead)
{
  size_t arraySize = 0;   
  char **lines = NULL;
  char *nextLine = NULL;

  *linesRead = 0;

  lines = malloc(INITIAL_SIZE * sizeof *lines);
  if (!lines)
  {
    fprintf(stderr, "Could not allocate array\n");
    return NULL;
  }

  arraySize = INITIAL_SIZE;

  /**
   * Read the next input line from the stream.  We're abstracting this
   * out to keep the code simple.
   */
  while ((nextLine = getNextLine(stream)))  
  {
    if (arraySize <= *linesRead)
    {
      char **tmp = realloc(lines, arraysSize * 2 * sizeof *tmp);
      if (tmp)
      {
        lines = tmp;
        arraySize *= 2;
      }
    }
    lines[(*linesRead)++] = nextLine;
  )

  return lines;
}
1 голос
/ 28 августа 2011

Хотя многомерный массив может решить эту проблему, прямоугольный 2D-массив на самом деле не будет естественным решением C.

Вот программа, которая сначала считывает файл в связанный список, а затем выделяет векторуказателей правильного размера.Каждый отдельный символ тогда появляется как array[line][col], но на самом деле каждая строка имеет длину, необходимую для этого.Это C99 за исключением <err.h>.

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

typedef struct strnode {
  char *s;
  struct strnode *next;
} strnode;

strnode *list_head;
strnode *list_last;

strnode *read1line(void) {
  char space[1024];
  if(fgets(space, sizeof space, stdin) == NULL)
    return NULL;
  strnode *node = malloc(sizeof(strnode));
  if(node && (node->s = malloc(strlen(space) + 1))) {
    strcpy(node->s, space);
    node->next = NULL;
    if (list_head == NULL)
      list_head = node;
    else
      list_last->next = node;
    list_last = node;
    return node;
  }
  err(1, NULL);
}

int main(int ac, char **av) {
  int n;
  strnode *s;

  for(n = 0; (s = read1line()) != NULL; ++n)
    continue;
  if(n > 0) {
    int i;
    strnode *b;
    char **a = malloc(n * sizeof(char *));
    printf("There were %d lines\n", n);
    for(b = list_head, i = 0; b; b = b->next, ++i)
      a[i] = b->s;
    printf("Near the middle is: %s", a[n / 2]);
  }
  return 0;
}
1 голос
/ 28 августа 2011

Вместо массива вы также можете использовать связанный список. Код проще, но распределение происходит чаще и может пострадать от фрагментации.

Пока вы не планируете делать много произвольного доступа (который здесь O (n)), итерация примерно так же проста, как и обычный массив.

typedef struct Line Line;
struct Line{
    char text[LINE_MAX];
    Line *next;
};

Line *mkline()
{
    Line *l = malloc(sizeof(Line));
    if(!l)
       error();
    return l;
}

main()
{
    Line *lines = mkline();
    Line *lp = lines;
    while(fgets(lp->text, sizeof lp->text, stdin)!=NULL){
         lp->next = mkline();
         lp = lp->next;
    }
    lp->next = NULL;
}
1 голос
/ 28 августа 2011

Если вы используете C, вам нужно будет самостоятельно изменить размер массива.C ++ и SDL сделали это для вас.Это называется vector.http://www.cplusplus.com/reference/stl/vector/

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...