Допустим, у вас есть функция, которая создает массив int
s:
int *create_int_array(const size_t num)
{
int *iarray;
size_t i;
if (num < 1)
return NULL; /* Let's not return an empty array. */
iarray = malloc(num * sizeof iarray[0]);
if (!iarray)
return NULL; /* Out of memory! */
/* Fill in the array with increasing integers. */
for (i = 0; i < num; i++)
iarray[i] = i + 1;
return iarray;
}
Предположим, у вас есть функция, которая вычисляет сумму целых чисел в массиве.Если мы игнорируем любые проблемы переполнения, это может выглядеть так:
int sum_int_array(const int *iarray, const size_t num)
{
int sum = 0;
size_t i;
/* Sum of an empty array is 0. */
if (num < 1)
return 0;
for (i = 0; i < num; i++)
sum += iarray[i];
return sum;
}
Обратите внимание, что sizeof
- это не функция, а ключевое слово языка Си.Его аргумент рассматривается только для его размера.Таким образом, sizeof iarray[0]
возвращает размер каждого элемента в iarray
и является полностью безопасным и допустимым, даже если iarray
не определен или равен NULL в этой точке.Вы часто видите эту идиому в программах на Си;научитесь читать его как "размер первого элемента iarray" , что совпадает с "размером каждого элемента в iarray" , поскольку все элементы массива C имеют одинаковый размер.
В вашем main()
вы могли бы назвать их так:
#ifndef NUM
#define NUM 5
#endif
int main(void)
{
int *array, result;
array = create_int_array(NUM);
if (!array) {
fprintf(stderr, "Out of memory!\n");
exit(EXIT_FAILURE);
}
result = sum_int_array(array, NUM);
printf("Sum is %d.\n", result);
free(array);
return EXIT_SUCCESS;
}
Как вы видите, на самом деле ничего особенного.Что ж, вам нужно ознакомиться с синтаксисом указателя.
(Правило, на которое я хотел бы обратить внимание, заключается в том, что при чтении типов указателей читайте спецификаторы справа налево, ограниченные *
, читаемые как указатель на . Таким образом, int *const a
читается как "a является константой, указатель на int" , а const char **b
читается как "b является указателем на aуказатель на const char ".)
В таких ситуациях структура , описывающая массив, имеет гораздо больший смысл.Например:
typedef struct {
size_t max; /* Maximum number of elements val[] can hold */
size_t num; /* Number of elements in val[] */
int *val;
} iarray;
#define IARRAY_INIT { 0, 0, NULL }
Идея состоит в том, что вы можете объявить переменную типа iarray
так же, как и любую другую переменную;но вы также инициализируете их в пустой массив, используя макрос IARRAY_INIT
.Другими словами, таким образом:
iarray my_array = IARRAY_INIT;
При этой инициализации структура всегда инициализируется в известное состояние, и нам не нужна отдельная функция инициализации.Нам действительно нужна только пара вспомогательных функций:
static inline void iarray_free(iarray *array)
{
if (array) {
free(array->val);
array->max = 0;
array->num = 0;
array->val = NULL;
}
}
/* Try to grow the array dynamically.
Returns the number of elements that can be added right now. */
static inline size_t iarray_need(iarray *array, const size_t more)
{
if (!array)
return 0;
if (array->num + more > array->max) {
size_t max = array->num + more;
void *val;
/* Optional: Growth policy. Instead of allocating exactly
as much memory as needed, we allocate more,
in the hopes that this reduces the number of
realloc() calls, which tend to be a bit slow.
However, we don't want to waste too much
memory by allocating and then not using it. */
if (max < 16) {
/* Always allocate at least 16 elements, */
max = 16;
} else
if (max < 65536) {
/* up to 65535 elements add 50% extra, */
max = (3*max) / 2;
} else {
/* then round up to next multiple of 65536, less 16. */
max = (max | 65535) + 65521;
}
val = realloc(array->val, max * sizeof array->val[0]);
if (!val) {
/* We cannot grow the array. However, the old
array is still intact; realloc() does not
free it if it fails. */
return array->max - array->num;
}
/* Note: the new elements in array->val,
array->val[array->max] to
array->val[max-1], inclusive,
are undefined. That is fine, usually,
but might be important in some special
cases like resizing hash tables or such. */
array->max = max;
array->val = val;
}
return array->max - array->num;
}
/* Optional; same as initializing the variable to IARRAY_INIT. */
static inline void iarray_init(iarray *array)
{
array->max = 0;
array->num = 0;
array->val = NULL;
}
Бит static inline
означает, что функции видны только в этом модуле компиляции, и компилятор может реализовать эту функцию непосредственно на сайте вызова.,В основном, static inline
используется для макроподобных функций и функций доступа.Если вы поместите структуру в файл заголовка (.h), вы также включите в нее соответствующие вспомогательные функции static inline
.
Часть policy policy является лишь примером,Если вы опускаете политику роста и всегда перераспределяете элементы array->num + more
, ваш код будет очень часто вызывать realloc()
, потенциально для каждого добавленного int
.В большинстве случаев выполнение этого часто замедляет вашу программу, потому что realloc()
(а также malloc()
, calloc()
) довольно медленные.Чтобы избежать этого, мы предпочитаем немного дополнить или округлить распределение: не слишком много, чтобы тратить выделенную, но неиспользуемую память, но достаточно, чтобы сохранить общую программу быстро, и не ставить узкие места на слишком много вызовов realloc()
.
«Хорошая политика роста» очень спорна и действительно зависит от поставленной задачи.Вышеприведенное должно действительно хорошо работать на всех современных операционных системах на настольных компьютерах, ноутбуках и планшетах, когда программе требуется только один или только несколько таких массивов.
(Если программа использует много таких массивов,он может реализовать функцию iarray_optimize()
, которая перераспределяет массив точно на количество элементов, которые он имеет. Когда массив вряд ли изменит размер в ближайшее время, вызов этой функции обеспечит не слишком большое использование памяти, не используемой, но распределенной в массивах.)
Давайте рассмотрим пример функции, которая использует вышеописанное.Скажем, очевидное: добавление целого числа к массиву:
/* Append an int to the array.
Returns 0 if success, nonzero if an error occurs.
*/
int iarray_append(iarray *array, int value)
{
if (!array)
return -1; /* NULL array specified! */
if (iarray_need(array, 1) < 1)
return -2; /* Not enough memory to grow the array. */
array->val[array->num++] = value;
return 0;
}
Другой пример функции, которая сортирует int
s в массиве по возрастанию или убыванию:
static int cmp_int_ascending(const void *ptr1, const void *ptr2)
{
const int val1 = *(const int *)ptr1;
const int val2 = *(const int *)ptr2;
return (val1 < val2) ? -1 :
(val1 > val2) ? +1 : 0;
}
static int cmp_int_descending(const void *ptr1, const void *ptr2)
{
const int val1 = *(const int *)ptr1;
const int val2 = *(const int *)ptr2;
return (val1 < val2) ? +1 :
(val1 > val2) ? -1 : 0;
}
static void iarray_sort(iarray *array, int direction)
{
if (array && array->num > 1) {
if (direction > 0)
qsort(array->val, array->num, sizeof array->val[0],
cmp_int_ascending);
else
if (direction < 0)
qsort(array->val, array->num, sizeof array->val[0],
cmp_int_descending);
}
}
Многие новые программисты не понимают, что стандартная библиотека C имеет эту изящную и довольно эффективную qsort()
функцию для сортировки массивов;все, что нужно, это функция сравнения.Если direction
положительно для iarray_sort()
, массив сортируется в порядке возрастания, сначала наименьший int
;если direction
отрицательно, то в порядке убывания сначала самое большое int
.
Простой пример main()
, который читает все действительные int
s из стандартного ввода, сортирует их и печатает их в порядке возрастания (возрастающее значение):
int main(void)
{
iarray array = IARRAY_INIT;
int value;
size_t i;
while (scanf(" %d", &value) == 1)
if (iarray_append(&array, value)) {
fprintf(stderr, "Out of memory.\n");
exit(EXIT_FAILURE);
}
iarray_sort(&array, +1); /* sort by increasing value */
for (i = 0; i < array.num; i++)
printf("%d\n", array.val[i]);
iarray_free(&array);
return EXIT_SUCCESS;
}