Как мне объявить массив неопределенного или без начального размера? - PullRequest
31 голосов
/ 04 декабря 2010

Я знаю, что это можно сделать с помощью malloc , но я пока не знаю, как его использовать.

Например, я хотел, чтобы пользователь вводил несколько чисел, используя бесконечный циклсо сторожем, чтобы поставить точку в этом (то есть -1), но так как я еще не знаю, сколько он / она будет вводить, я должен объявить массив без начального размера, но я также знаю, что он выигралне работает как этот int arr [];во время компиляции, так как он должен иметь определенное количество элементов.

Объявление его с преувеличенным размером, например int arr [1000]; будет работать, но он чувствует себя глупым (он выделил бы 1000 целочисленных байтов в память), и я хотел бы узнать более элегантный способ сделать это.

Ответы [ 7 ]

34 голосов
/ 04 декабря 2010

Это может быть сделано с помощью указателя и выделения памяти в куче с помощью malloc.Обратите внимание, что позже невозможно спросить, насколько велик этот блок памяти.Вы должны сами следить за размером массива.

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

int main(int argc, char** argv)
{
  /* declare a pointer do an integer */
  int *data; 
  /* we also have to keep track of how big our array is - I use 50 as an example*/
  const int datacount = 50;
  data = malloc(sizeof(int) * datacount); /* allocate memory for 50 int's */
  if (!data) { /* If data == 0 after the call to malloc, allocation failed for some reason */
    perror("Error allocating memory");
    abort();
  }
  /* at this point, we know that data points to a valid block of memory.
     Remember, however, that this memory is not initialized in any way -- it contains garbage.
     Let's start by clearing it. */
  memset(data, 0, sizeof(int)*datacount);
  /* now our array contains all zeroes. */
  data[0] = 1;
  data[2] = 15;
  data[49] = 66; /* the last element in our array, since we start counting from 0 */
  /* Loop through the array, printing out the values (mostly zeroes, but even so) */
  for(int i = 0; i < datacount; ++i) {
    printf("Element %d: %d\n", i, data[i]);
  }
}

Вот и все.Ниже приведено более подробное объяснение того, почему это работает:)

Я не знаю, насколько хорошо вы знаете указатели C, но доступ к массиву в C (например, array[2]) на самом деле является сокращением для доступа к памяти черезуказательЧтобы получить доступ к памяти, на которую указывает data, вы пишете *data.Это называется разыменованием указателя.Поскольку data имеет тип int *, то *data имеет тип int.Теперь важная информация: (data + 2) означает «добавьте размер байта в 2 дюйма к адресу, на который указывает data».

Массив в C - это просто последовательность значений в соседней памяти,array[1] находится рядом с array[0].Поэтому, когда мы выделяем большой блок памяти и хотим использовать его в качестве массива, нам нужен простой способ получить прямой адрес для каждого элемента внутри.К счастью, C позволяет нам использовать обозначения массива и для указателей.data[0] означает то же самое, что и *(data+0), а именно «доступ к памяти, на которую указывает data».data[2] означает *(data+2) и обращается к третьему int в блоке памяти.

13 голосов
/ 04 декабря 2010

Часто это делается следующим образом:

  • выделяет массив некоторого начального (довольно небольшого) размера;
  • считывает в этот массив, отслеживая, сколько элементоввы прочитали;
  • как только массив заполнится, перераспределите его, удвоив размер и сохранив (т.е. скопировав) содержимое;
  • повторите до завершения.

Я считаю, что этот шаблон встречается довольно часто.

Что интересно в этом методе, так это то, что он позволяет вставлять N элементов в пустой массив один за другим в амортизированном O(N) времени, не знаяN заранее.

5 голосов
/ 04 декабря 2010

Modern C, он же C99, имеет массивы переменной длины , VLA.К сожалению, не все компиляторы поддерживают это, но если у вас это будет альтернативой.

2 голосов
/ 04 декабря 2010

Попробуйте реализовать динамическую структуру данных, такую ​​как связанный список

1 голос
/ 04 декабря 2010

Вот пример программы, которая читает stdin в буфер памяти, который увеличивается по мере необходимости. Это достаточно просто, чтобы дать представление о том, как вы можете справиться с такими вещами. Одна вещь, которую, вероятно, можно было бы сделать по-другому в реальной программе, это то, как массив должен увеличиваться при каждом выделении - я оставил его здесь небольшим, чтобы упростить задачу, если вы хотите пройти через отладчик. Реальная программа, вероятно, будет использовать гораздо больший прирост выделения (часто размер выделения удваивается, но если вы собираетесь это сделать, вам, вероятно, следует «ограничить» приращение в некотором разумном размере - возможно, не имеет смысла удваивать выделение, когда вы попадаете в сотни мегабайт).

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

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


void fatal_error(void);

int main( int argc, char** argv)
{
    int buf_size = 0;
    int buf_used = 0;

    char* buf = NULL;
    char* tmp = NULL;    

    char c;
    int i = 0;

    while ((c = getchar()) != EOF) {
        if (buf_used == buf_size) {
             //need more space in the array

             buf_size += 20;
             tmp = realloc(buf, buf_size); // get a new larger array
             if (!tmp) fatal_error();

             buf = tmp;
        }

        buf[buf_used] = c; // pointer can be indexed like an array
        ++buf_used;
    }

    puts("\n\n*** Dump of stdin ***\n");

    for (i = 0; i < buf_used; ++i) {
        putchar(buf[i]);
    }

    free(buf);

    return 0;
}

void fatal_error(void)
{
    fputs("fatal error - out of memory\n", stderr);
    exit(1);
}

Этот пример в сочетании с примерами в других ответах должен дать вам представление о том, как с такими вещами обращаться на низком уровне.

1 голос
/ 04 декабря 2010

malloc() (и его друзья free() и realloc()) - это способ сделать это в C.

0 голосов
/ 04 декабря 2010

Один способ, который я могу себе представить, - это использовать связанный список для реализации такого сценария, если вам нужно ввести все числа до того, как пользователь введет что-то, что указывает на завершение цикла.(Публикация в качестве первого варианта, потому что никогда не делала этого для пользовательского ввода, это просто казалось интересным. Напрасно, но художественно )

Другой способ - это буферизованный ввод.Выделите буфер, заполните его, перераспределите, если цикл продолжается ( не элегантно, но наиболее рационально для данного варианта использования ).

Я не рассматриваю описанноебыть элегантным.Возможно, я бы изменил вариант использования ( наиболее рациональный ).

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