#c #указатели
Наткнулся на код, по которому у меня возникает пару вопросов, если ответите, то буду признателен #include#include int isOdd(int a) { return (a % 2 != 0); } unsigned int filter(int *arr, unsigned size, int (*pred)(int), int** out) { unsigned i; unsigned j; *out = (int*) malloc(sizeof(int)*size); for (i = 0, j = 0; i < size; i++) { if (pred(arr[i])) { (*out)[j] = arr[i]; j++; } } *out = (int*) realloc(*out, j*sizeof(int)); return j; } int main () { int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; unsigned i; unsigned size; int *aOdd = NULL; size = filter(a, 10, isOdd, &aOdd); for (i = 0; i < size; i++) { printf("%d ", aOdd[i]); } } Вопрос №1: Зачем требуются явные преобразования такого вида? *out = (int*) malloc(sizeof(int)*size); *out = (int*) realloc(*out, j*sizeof(int)); Они же излишни, если я не ошибаюсь. Вопрос №2: Что происходит в строках, которые я выделил восклицательными знаками? unsigned int filter(int *arr, unsigned size, int (*pred)(int), int** out) { unsigned i; unsigned j; *out = (int*) malloc(sizeof(int)*size); /* !!!!!!!!!! */ for (i = 0, j = 0; i < size; i++) { if (pred(arr[i])) { (*out)[j] = arr[i]; /* !!!!!!!!!! */ j++; } } *out = (int*) realloc(*out, j*sizeof(int)); /* !!!!!!!!!! */ return j; } Вопрос №3: Разве не легче было реализовать функцию из вопроса №2 таким образом (просто через указатель, а не через указатель на указатель) unsigned int filter(int *arr, unsigned size, int (*pred)(int), int* out) { unsigned i; unsigned j; for (i = 0, j = 0; i < size; i++) { if (pred(arr[i])) { *(out + j) = arr[i]; j++; } } return j; } Есть ли существенные различия или даже преимущества между функцией из вопроса №2 и функцией из вопроса №3?
Ответы
Ответ 1
Вопрос №1: Зачем требуются явные преобразования такого вида? Для C не нужны. Для C++ необходимы, т.к. в C++ неявное преобразование из void* запрещено. Вопрос №2: Что происходит в строках, которые я выделил восклицательными знаками? 1) Выделение памяти для size int'ов. 2) Копирование элемента, удовлетворяющего предикату в только что выделенный массив. 3) Отрезаем не заполненный кусок массива данных Вопрос №3: Разве не легче было реализовать функцию из вопроса Они дают разный результат Есть ли существенные различия или даже преимущества между функцией из вопроса №2 и функцией из вопроса №3? Для варианта из вопроса №2 память выделяется непосредственно в функции, а потом усекается. Для варианта из третьего вопроса эта ответственность ляжет на вызывающую сторону. Преимущества и недостатки есть у обоих способов. Всё зависит от того, как их использовать.Ответ 2
Я уже писал об этом раньше. Когда вы реализуете функцию, размер массива-результата которой заранее неизвестен, т.е. становится известен только внутри функции, то возникает проблема выделения правильного количества памяти для сохранения результата. Пользователь (т.е. вызывающий код) в общем случае заранее эту память выделить не может, ибо пока еще не знает, сколько памяти понадобится. В таких случаях есть несколько основных стратегий: В некоторых случаях заранее известен максимально возможный размер результата, можно выделить память заранее "по максимуму" и такое максимальное выделение является, условно выражаясь, "разумным". Это как раз ваш случай. И да, ваш №3 будет прекрасно работать по этой схеме. Пользователь только обязан предоставить выходной массив размера не менее size и иметь в виду, что в общем случае не весь он будет использован. Выделять память внутри функции: либо сразу, когда ее размер становится известен, либо инкрементально по мере накопления результата, либо "по максимуму" с последующим урезанием, либо еще как. Это ваш вариант №2. Проблема этого подхода в том, что выделение памяти в таком случае нелегко кастомизировать. В простейшем случае будет просто malloc, как у вас в примере. А может пользователю не нужен malloc? Реализовать функцию по образу и подобию snprintf, т.е. предоставить возможность "холостого" прохода, который лишь подсчитывает и возвращает размер результата, но пока не формирует результат. Получив подсчитанный размер, пользователь выделяет память любым удобным для него образом и затем снова вызывает ту же функцию, теперь уже для "настоящего" прохода, т.е. для формирования результата в буфере. То есть ваши №2 и №3 - два альтернативных подхода к решению задачи. Оба способа по-своему правильны.Ответ 3
В дополнение к ответу Groessmah добавлю пояснение к 3-му вопросу: Ваша ф-ция реализована ошибочно: во-первых отсутствует выделение памяти - у вас указатель все еще указывает на NULL! А для того, чтобы выделить память в ф-ции и нужен указатель на указатель, иначе память выделить вы сможете, но вот сделат так, чтобы нужный нам указатель указывал на нее - нет. Во-вторых, если мы и выделим память до вызова ф-ции, урезать ее в ф-ции, опять таки, не получится.
Комментариев нет:
Отправить комментарий