Как сделать массив динамического размера в C?

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

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

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

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

Любые идеи или даже намек в правильном направлении? Я ценю любую помощь.

Ответ 1

Чтобы динамически выделить 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 до предыдущего. Вы можете либо перейти от начала одиночного связанного списка, либо пройти с начала или использовать двойные ссылки.

Ответ 2

В C. Нет стандартного типа изменяемого массива в 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 s. Поскольку у C нет шаблонов, вам придется либо поместить весь этот код в макрос для каждого типа массива (или использовать другой препроцессор, например GNU m4). Или вы можете использовать общий контейнер массива, в котором либо используются указатели void* (требующие, чтобы все элементы массива были malloc 'ed), либо непрозрачные ячейки памяти, для которых требовалось бы приведение с каждым доступом к элементу и memcpy для каждого элемент get/set.

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

Ответ 3

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

Ответ 4

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

Пока вы не планируете делать много произвольного доступа (здесь 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;
}

Ответ 5

В то время как многомерный массив может решить эту проблему, прямоугольная 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;
}

Ответ 6

Вы можете использовать функции 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;
}