Подтвердить что ты не робот

Как объявить массив undefined или нет начального размера?

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

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

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

4b9b3361

Ответ 1

Это можно сделать с помощью указателя и выделения памяти в куче с помощью 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 */
  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 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 ints к адресу, на который указывает data".

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

Ответ 2

То, как это часто делается, выглядит следующим образом:

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

Я нахожу, что этот шаблон довольно часто встречается.

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

Ответ 3

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

Ответ 4

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

Ответ 5

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

Ответ 6

Вот пример программы, которая читает 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);
}

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

Ответ 7

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

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

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